CycleGAN, Image-to-Image Translation

In this notebook, we're going to define and train a CycleGAN to read in an image from a set $X$ and transform it so that it looks as if it belongs in set $Y$. Specifically, we'll look at a set of images of Yosemite national park taken either during the summer of winter. The seasons are our two domains!

The objective will be to train generators that learn to transform an image from domain $X$ into an image that looks like it came from domain $Y$ (and vice versa).

Some examples of image data in both sets are pictured below.

Unpaired Training Data

These images do not come with labels, but CycleGANs give us a way to learn the mapping between one image domain and another using an unsupervised approach. A CycleGAN is designed for image-to-image translation and it learns from unpaired training data. This means that in order to train a generator to translate images from domain $X$ to domain $Y$, we do not have to have exact correspondences between individual images in those domains. For example, in the paper that introduced CycleGANs, the authors are able to translate between images of horses and zebras, even though there are no images of a zebra in exactly the same position as a horse or with exactly the same background, etc. Thus, CycleGANs enable learning a mapping from one domain $X$ to another domain $Y$ without having to find perfectly-matched, training pairs!

CycleGAN and Notebook Structure

A CycleGAN is made of two types of networks: discriminators, and generators. In this example, the discriminators are responsible for classifying images as real or fake (for both $X$ and $Y$ kinds of images). The generators are responsible for generating convincing, fake images for both kinds of images.

This notebook will detail the steps you should take to define and train such a CycleGAN.

  1. You'll load in the image data using PyTorch's DataLoader class to efficiently read in images from a specified directory.
  2. Then, you'll be tasked with defining the CycleGAN architecture according to provided specifications. You'll define the discriminator and the generator models.
  3. You'll complete the training cycle by calculating the adversarial and cycle consistency losses for the generator and discriminator network and completing a number of training epochs. It's suggested that you enable GPU usage for training.
  4. Finally, you'll evaluate your model by looking at the loss over time and looking at sample, generated images.

Load and Visualize the Data

We'll first load in and visualize the training data, importing the necessary libraries to do so.

In [1]:
!unzip summer2winter_yosemite.zip # can comment out after executing once
Archive:  summer2winter_yosemite.zip
replace summer2winter_yosemite/.DS_Store? [y]es, [n]o, [A]ll, [N]one, [r]ename: ^C
In [2]:
# loading in and transforming data
import os
import torch
from torch.utils.data import DataLoader
import torchvision
import torchvision.datasets as datasets
import torchvision.transforms as transforms

# visualizing data
import matplotlib.pyplot as plt
import numpy as np
import warnings

%matplotlib inline

DataLoaders

The get_data_loader function returns training and test DataLoaders that can load data efficiently and in specified batches. The function has the following parameters:

  • image_type: summer or winter, the names of the directories where the X and Y images are stored
  • image_dir: name of the main image directory, which holds all training and test images
  • image_size: resized, square image dimension (all images will be resized to this dim)
  • batch_size: number of images in one batch of data

The test data is strictly for feeding to our generators, later on, so we can visualize some generated samples on fixed, test data.

You can see that this function is also responsible for making sure our images are of the right, square size (128x128x3) and converted into Tensor image types.

It's suggested that you use the default values of these parameters.

Note: If you are trying this code on a different set of data, you may get better results with larger image_size and batch_size parameters. If you change the batch_size, make sure that you create complete batches in the training loop otherwise you may get an error when trying to save sample data.

In [3]:
def get_data_loader(image_type, image_dir='summer2winter_yosemite', 
                    image_size=128, batch_size=16, num_workers=0):
    """Returns training and test data loaders for a given image type, either 'summer' or 'winter'. 
       These images will be resized to 128x128x3, by default, converted into Tensors, and normalized.
    """
    
    # resize and normalize the images
    transform = transforms.Compose([transforms.Resize(image_size), # resize to 128x128
                                    transforms.ToTensor()])

    # get training and test directories
    image_path = './' + image_dir
    train_path = os.path.join(image_path, image_type)
    test_path = os.path.join(image_path, 'test_{}'.format(image_type))

    # define datasets using ImageFolder
    train_dataset = datasets.ImageFolder(train_path, transform)
    test_dataset = datasets.ImageFolder(test_path, transform)

    # create and return DataLoaders
    train_loader = DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=True, num_workers=num_workers)
    test_loader = DataLoader(dataset=test_dataset, batch_size=batch_size, shuffle=False, num_workers=num_workers)

    return train_loader, test_loader
In [4]:
# Create train and test dataloaders for images from the two domains X and Y
# image_type = directory names for our data
dataloader_X, test_dataloader_X = get_data_loader(image_type='summer')
dataloader_Y, test_dataloader_Y = get_data_loader(image_type='winter')

Display some Training Images

Below we provide a function imshow that reshape some given images and converts them to NumPy images so that they can be displayed by plt. This cell should display a grid that contains a batch of image data from set $X$.

In [5]:
# helper imshow function
def imshow(img):
    npimg = img.numpy()
    plt.imshow(np.transpose(npimg, (1, 2, 0)))
    

# get some images from X
dataiter = iter(dataloader_X)
# the "_" is a placeholder for no labels
images, _ = dataiter.next()

# show images
fig = plt.figure(figsize=(12, 8))
imshow(torchvision.utils.make_grid(images))

Next, let's visualize a batch of images from set $Y$.

In [6]:
# get some images from Y
dataiter = iter(dataloader_Y)
images, _ = dataiter.next()

# show images
fig = plt.figure(figsize=(12,8))
imshow(torchvision.utils.make_grid(images))

Pre-processing: scaling from -1 to 1

We need to do a bit of pre-processing; we know that the output of our tanh activated generator will contain pixel values in a range from -1 to 1, and so, we need to rescale our training images to a range of -1 to 1. (Right now, they are in a range from 0-1.)

In [7]:
# current range
img = images[0]

print('Min: ', img.min())
print('Max: ', img.max())
Min:  tensor(0.)
Max:  tensor(0.9451)
In [8]:
# helper scale function
def scale(x, feature_range=(-1, 1)):
    ''' Scale takes in an image x and returns that image, scaled
       with a feature_range of pixel values from -1 to 1. 
       This function assumes that the input x is already scaled from 0-255.'''
    
    # scale from 0-1 to feature_range
    min, max = feature_range
    x = x * (max - min) + min
    return x
In [9]:
# scaled range
scaled_img = scale(img)

print('Scaled min: ', scaled_img.min())
print('Scaled max: ', scaled_img.max())
Scaled min:  tensor(-1.)
Scaled max:  tensor(0.8902)

Define the Model

A CycleGAN is made of two discriminator and two generator networks.

Discriminators

The discriminators, $D_X$ and $D_Y$, in this CycleGAN are convolutional neural networks that see an image and attempt to classify it as real or fake. In this case, real is indicated by an output close to 1 and fake as close to 0. The discriminators have the following architecture:

This network sees a 128x128x3 image, and passes it through 5 convolutional layers that downsample the image by a factor of 2. The first four convolutional layers have a BatchNorm and ReLu activation function applied to their output, and the last acts as a classification layer that outputs one value.

Convolutional Helper Function

To define the discriminators, you're expected to use the provided conv function, which creates a convolutional layer + an optional batch norm layer.

In [10]:
import torch.nn as nn
import torch.nn.functional as F

# helper conv function
def conv(in_channels, out_channels, kernel_size, stride=2, padding=1, batch_norm=True):
    """Creates a convolutional layer, with optional batch normalization.
    """
    layers = []
    conv_layer = nn.Conv2d(in_channels=in_channels, out_channels=out_channels, 
                           kernel_size=kernel_size, stride=stride, padding=padding, bias=False)
    
    layers.append(conv_layer)

    if batch_norm:
        layers.append(nn.BatchNorm2d(out_channels))
    return nn.Sequential(*layers)

Define the Discriminator Architecture

Your task is to fill in the __init__ function with the specified 5 layer conv net architecture. Both $D_X$ and $D_Y$ have the same architecture, so we only need to define one class, and later instantiate two discriminators.

It's recommended that you use a kernel size of 4x4 and use that to determine the correct stride and padding size for each layer. This Stanford resource may also help in determining stride and padding sizes.

  • Define your convolutional layers in __init__
  • Then fill in the forward behavior of the network

The forward function defines how an input image moves through the discriminator, and the most important thing is to pass it through your convolutional layers in order, with a ReLu activation function applied to all but the last layer.

You should not apply a sigmoid activation function to the output, here, and that is because we are planning on using a squared error loss for training. And you can read more about this loss function, later in the notebook.

In [11]:
class Discriminator(nn.Module):
    
    def __init__(self, conv_dim=64):
        super(Discriminator, self).__init__()

        # Define all convolutional layers
        # Should accept an RGB image as input and output a single value
        self.conv1 = conv(3, conv_dim, kernel_size=4, batch_norm=False) #64, 64
        self.conv2 = conv(conv_dim, conv_dim*2, kernel_size=4) #32, 32, 128
        self.conv3 = conv(conv_dim*2, conv_dim*4, kernel_size=4) #16, 16, 256
        self.conv4 = conv(conv_dim*4, conv_dim*8, kernel_size=4) #8, 8, 512
        #Classification layer
        self.conv5 = conv(conv_dim*8, 1, kernel_size=4, stride=1, batch_norm=False) #Using stride 1 as it is not a strided layer
        
    def forward(self, x):
        # define feedforward behavior
        x = F.relu(self.conv1(x))
        x = F.relu(self.conv2(x))
        x = F.relu(self.conv3(x))
        x = F.relu(self.conv4(x))
        out = self.conv5(x)
        
        return out

Generators

The generators, G_XtoY and G_YtoX (sometimes called F), are made of an encoder, a conv net that is responsible for turning an image into a smaller feature representation, and a decoder, a transpose_conv net that is responsible for turning that representation into an transformed image. These generators, one from XtoY and one from YtoX, have the following architecture:

This network sees a 128x128x3 image, compresses it into a feature representation as it goes through three convolutional layers and reaches a series of residual blocks. It goes through a few (typically 6 or more) of these residual blocks, then it goes through three transpose convolutional layers (sometimes called de-conv layers) which upsample the output of the resnet blocks and create a new image!

Note that most of the convolutional and transpose-convolutional layers have BatchNorm and ReLu functions applied to their outputs with the exception of the final transpose convolutional layer, which has a tanh activation function applied to the output. Also, the residual blocks are made of convolutional and batch normalization layers, which we'll go over in more detail, next.


Residual Block Class

To define the generators, you're expected to define a ResidualBlock class which will help you connect the encoder and decoder portions of the generators. You might be wondering, what exactly is a Resnet block? It may sound familiar from something like ResNet50 for image classification, pictured below.

ResNet blocks rely on connecting the output of one layer with the input of an earlier layer. The motivation for this structure is as follows: very deep neural networks can be difficult to train. Deeper networks are more likely to have vanishing or exploding gradients and, therefore, have trouble reaching convergence; batch normalization helps with this a bit. However, during training, we often see that deep networks respond with a kind of training degradation. Essentially, the training accuracy stops improving and gets saturated at some point during training. In the worst cases, deep models would see their training accuracy actually worsen over time!

One solution to this problem is to use Resnet blocks that allow us to learn so-called residual functions as they are applied to layer inputs. You can read more about this proposed architecture in the paper, Deep Residual Learning for Image Recognition by Kaiming He et. al, and the below image is from that paper.

Residual Functions

Usually, when we create a deep learning model, the model (several layers with activations applied) is responsible for learning a mapping, M, from an input x to an output y.

M(x) = y (Equation 1)

Instead of learning a direct mapping from x to y, we can instead define a residual function

F(x) = M(x) - x

This looks at the difference between a mapping applied to x and the original input, x. F(x) is, typically, two convolutional layers + normalization layer and a ReLu in between. These convolutional layers should have the same number of inputs as outputs. This mapping can then be written as the following; a function of the residual function and the input x. The addition step creates a kind of loop that connects the input x to the output, y:

M(x) = F(x) + x (Equation 2) or

y = F(x) + x (Equation 3)

Optimizing a Residual Function

The idea is that it is easier to optimize this residual function F(x) than it is to optimize the original mapping M(x). Consider an example; what if we want y = x?

From our first, direct mapping equation, Equation 1, we could set M(x) = x but it is easier to solve the residual equation F(x) = 0, which, when plugged in to Equation 3, yields y = x.

Defining the ResidualBlock Class

To define the ResidualBlock class, we'll define residual functions (a series of layers), apply them to an input x and add them to that same input. This is defined just like any other neural network, with an __init__ function and the addition step in the forward function.

In our case, you'll want to define the residual block as:

  • Two convolutional layers with the same size input and output
  • Batch normalization applied to the outputs of the convolutional layers
  • A ReLu function on the output of the first convolutional layer

Then, in the forward function, add the input x to this residual block. Feel free to use the helper conv function from above to create this block.

In [12]:
# residual block class
class ResidualBlock(nn.Module):
    """Defines a residual block.
       This adds an input x to a convolutional layer (applied to x) with the same size input and output.
       These blocks allow a model to learn an effective transformation from one domain to another.
    """
    def __init__(self, conv_dim):
        super(ResidualBlock, self).__init__()
        # conv_dim = number of inputs  
        
        # define two convolutional layers + batch normalization that will act as our residual function, F(x)
        # layers should have the same shape input as output; I suggest a kernel_size of 3
        self.conv1 = conv(conv_dim, conv_dim, kernel_size=3, stride=1, padding=1) #stride and padding so x y do not change
        self.conv2 = conv(conv_dim, conv_dim, kernel_size=3, stride=1, padding=1) #stride and padding so x y do not change
        
        
    def forward(self, x):
        # apply a ReLu activation the outputs of the first layer
        # return a summed output, x + resnet_block(x)
        x = F.relu(self.conv1(x))
        x = x + self.conv2(x)
        return x
    

Transpose Convolutional Helper Function

To define the generators, you're expected to use the above conv function, ResidualBlock class, and the below deconv helper function, which creates a transpose convolutional layer + an optional batchnorm layer.

In [13]:
# helper deconv function
def deconv(in_channels, out_channels, kernel_size, stride=2, padding=1, batch_norm=True):
    """Creates a transpose convolutional layer, with optional batch normalization.
    """
    layers = []
    # append transpose conv layer
    layers.append(nn.ConvTranspose2d(in_channels, out_channels, kernel_size, stride, padding, bias=False))
    # optional batch norm layer
    if batch_norm:
        layers.append(nn.BatchNorm2d(out_channels))
    return nn.Sequential(*layers)

Define the Generator Architecture

  • Complete the __init__ function with the specified 3 layer encoder convolutional net, a series of residual blocks (the number of which is given by n_res_blocks), and then a 3 layer decoder transpose convolutional net.
  • Then complete the forward function to define the forward behavior of the generators. Recall that the last layer has a tanh activation function.

Both $G_{XtoY}$ and $G_{YtoX}$ have the same architecture, so we only need to define one class, and later instantiate two generators.

In [14]:
class CycleGenerator(nn.Module):
    
    def __init__(self, conv_dim=64, n_res_blocks=6):
        super(CycleGenerator, self).__init__()

        # 1. Define the encoder part of the generator
        self.encoder_conv1 = conv(3, conv_dim, kernel_size=4)
        self.encoder_conv2 = conv(conv_dim, conv_dim*2, kernel_size=4)
        self.encoder_conv3 = conv(conv_dim*2, conv_dim*4, kernel_size=4)

        # 2. Define the resnet part of the generator
        res_layers = []
        for _ in range(n_res_blocks):
            res_layers.append(ResidualBlock(conv_dim*4))
        self.res_blocks = nn.Sequential(*res_layers)
            
        # 3. Define the decoder part of the generator
        self.decoder_deconv1 = deconv(conv_dim*4, conv_dim*2, kernel_size=4)
        self.decoder_deconv2 = deconv(conv_dim*2, conv_dim, kernel_size=4)
        self.decoder_deconv3 = deconv(conv_dim, 3, kernel_size=4, batch_norm=False)
        
    def forward(self, x):
        """Given an image x, returns a transformed image."""
        # define feedforward behavior, applying activations as necessary
        x = F.relu(self.encoder_conv1(x))
        x = F.relu(self.encoder_conv2(x))
        x = F.relu(self.encoder_conv3(x))
        x = self.res_blocks(x)
        x = F.relu(self.decoder_deconv1(x))
        x = F.relu(self.decoder_deconv2(x))
        x = F.tanh(self.decoder_deconv3(x))
        
        return x

Create the complete network

Using the classes you defined earlier, you can define the discriminators and generators necessary to create a complete CycleGAN. The given parameters should work for training.

First, create two discriminators, one for checking if $X$ sample images are real, and one for checking if $Y$ sample images are real. Then the generators. Instantiate two of them, one for transforming a painting into a realistic photo and one for transforming a photo into into a painting.

In [15]:
def create_model(g_conv_dim=64, d_conv_dim=64, n_res_blocks=6):
    """Builds the generators and discriminators."""
    
    # Instantiate generators
    G_XtoY = CycleGenerator(g_conv_dim, n_res_blocks)
    G_YtoX = CycleGenerator(g_conv_dim, n_res_blocks)
    # Instantiate discriminators
    D_X = Discriminator(d_conv_dim)
    D_Y = Discriminator(d_conv_dim)

    # move models to GPU, if available
    if torch.cuda.is_available():
        device = torch.device("cuda:0")
        G_XtoY.to(device)
        G_YtoX.to(device)
        D_X.to(device)
        D_Y.to(device)
        print('Models moved to GPU.')
    else:
        print('Only CPU available.')

    return G_XtoY, G_YtoX, D_X, D_Y
In [16]:
# call the function to get models
G_XtoY, G_YtoX, D_X, D_Y = create_model()
Models moved to GPU.

Check that you've implemented this correctly

The function create_model should return the two generator and two discriminator networks. After you've defined these discriminator and generator components, it's good practice to check your work. The easiest way to do this is to print out your model architecture and read through it to make sure the parameters are what you expected. The next cell will print out their architectures.

In [17]:
# helper function for printing the model architecture
def print_models(G_XtoY, G_YtoX, D_X, D_Y):
    """Prints model information for the generators and discriminators.
    """
    print("                     G_XtoY                    ")
    print("-----------------------------------------------")
    print(G_XtoY)
    print()

    print("                     G_YtoX                    ")
    print("-----------------------------------------------")
    print(G_YtoX)
    print()

    print("                      D_X                      ")
    print("-----------------------------------------------")
    print(D_X)
    print()

    print("                      D_Y                      ")
    print("-----------------------------------------------")
    print(D_Y)
    print()
    

# print all of the models
print_models(G_XtoY, G_YtoX, D_X, D_Y)
                     G_XtoY                    
-----------------------------------------------
CycleGenerator(
  (encoder_conv1): Sequential(
    (0): Conv2d(3, 64, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False)
    (1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  )
  (encoder_conv2): Sequential(
    (0): Conv2d(64, 128, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False)
    (1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  )
  (encoder_conv3): Sequential(
    (0): Conv2d(128, 256, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False)
    (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  )
  (res_blocks): Sequential(
    (0): ResidualBlock(
      (conv1): Sequential(
        (0): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      )
      (conv2): Sequential(
        (0): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      )
    )
    (1): ResidualBlock(
      (conv1): Sequential(
        (0): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      )
      (conv2): Sequential(
        (0): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      )
    )
    (2): ResidualBlock(
      (conv1): Sequential(
        (0): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      )
      (conv2): Sequential(
        (0): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      )
    )
    (3): ResidualBlock(
      (conv1): Sequential(
        (0): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      )
      (conv2): Sequential(
        (0): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      )
    )
    (4): ResidualBlock(
      (conv1): Sequential(
        (0): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      )
      (conv2): Sequential(
        (0): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      )
    )
    (5): ResidualBlock(
      (conv1): Sequential(
        (0): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      )
      (conv2): Sequential(
        (0): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      )
    )
  )
  (decoder_deconv1): Sequential(
    (0): ConvTranspose2d(256, 128, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False)
    (1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  )
  (decoder_deconv2): Sequential(
    (0): ConvTranspose2d(128, 64, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False)
    (1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  )
  (decoder_deconv3): Sequential(
    (0): ConvTranspose2d(64, 3, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False)
  )
)

                     G_YtoX                    
-----------------------------------------------
CycleGenerator(
  (encoder_conv1): Sequential(
    (0): Conv2d(3, 64, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False)
    (1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  )
  (encoder_conv2): Sequential(
    (0): Conv2d(64, 128, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False)
    (1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  )
  (encoder_conv3): Sequential(
    (0): Conv2d(128, 256, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False)
    (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  )
  (res_blocks): Sequential(
    (0): ResidualBlock(
      (conv1): Sequential(
        (0): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      )
      (conv2): Sequential(
        (0): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      )
    )
    (1): ResidualBlock(
      (conv1): Sequential(
        (0): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      )
      (conv2): Sequential(
        (0): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      )
    )
    (2): ResidualBlock(
      (conv1): Sequential(
        (0): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      )
      (conv2): Sequential(
        (0): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      )
    )
    (3): ResidualBlock(
      (conv1): Sequential(
        (0): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      )
      (conv2): Sequential(
        (0): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      )
    )
    (4): ResidualBlock(
      (conv1): Sequential(
        (0): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      )
      (conv2): Sequential(
        (0): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      )
    )
    (5): ResidualBlock(
      (conv1): Sequential(
        (0): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      )
      (conv2): Sequential(
        (0): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      )
    )
  )
  (decoder_deconv1): Sequential(
    (0): ConvTranspose2d(256, 128, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False)
    (1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  )
  (decoder_deconv2): Sequential(
    (0): ConvTranspose2d(128, 64, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False)
    (1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  )
  (decoder_deconv3): Sequential(
    (0): ConvTranspose2d(64, 3, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False)
  )
)

                      D_X                      
-----------------------------------------------
Discriminator(
  (conv1): Sequential(
    (0): Conv2d(3, 64, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False)
  )
  (conv2): Sequential(
    (0): Conv2d(64, 128, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False)
    (1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  )
  (conv3): Sequential(
    (0): Conv2d(128, 256, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False)
    (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  )
  (conv4): Sequential(
    (0): Conv2d(256, 512, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False)
    (1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  )
  (conv5): Sequential(
    (0): Conv2d(512, 1, kernel_size=(4, 4), stride=(1, 1), padding=(1, 1), bias=False)
  )
)

                      D_Y                      
-----------------------------------------------
Discriminator(
  (conv1): Sequential(
    (0): Conv2d(3, 64, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False)
  )
  (conv2): Sequential(
    (0): Conv2d(64, 128, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False)
    (1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  )
  (conv3): Sequential(
    (0): Conv2d(128, 256, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False)
    (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  )
  (conv4): Sequential(
    (0): Conv2d(256, 512, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1), bias=False)
    (1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  )
  (conv5): Sequential(
    (0): Conv2d(512, 1, kernel_size=(4, 4), stride=(1, 1), padding=(1, 1), bias=False)
  )
)

Discriminator and Generator Losses

Computing the discriminator and the generator losses are key to getting a CycleGAN to train.

Image from original paper by Jun-Yan Zhu et. al.

  • The CycleGAN contains two mapping functions $G: X \rightarrow Y$ and $F: Y \rightarrow X$, and associated adversarial discriminators $D_Y$ and $D_X$. (a) $D_Y$ encourages $G$ to translate $X$ into outputs indistinguishable from domain $Y$, and vice versa for $D_X$ and $F$.

  • To further regularize the mappings, we introduce two cycle consistency losses that capture the intuition that if we translate from one domain to the other and back again we should arrive at where we started. (b) Forward cycle-consistency loss and (c) backward cycle-consistency loss.

Least Squares GANs

We've seen that regular GANs treat the discriminator as a classifier with the sigmoid cross entropy loss function. However, this loss function may lead to the vanishing gradients problem during the learning process. To overcome such a problem, we'll use a least squares loss function for the discriminator. This structure is also referred to as a least squares GAN or LSGAN, and you can read the original paper on LSGANs, here. The authors show that LSGANs are able to generate higher quality images than regular GANs and that this loss type is a bit more stable during training!

Discriminator Losses

The discriminator losses will be mean squared errors between the output of the discriminator, given an image, and the target value, 0 or 1, depending on whether it should classify that image as fake or real. For example, for a real image, x, we can train $D_X$ by looking at how close it is to recognizing and image x as real using the mean squared error:

out_x = D_X(x)
real_err = torch.mean((out_x-1)**2)

Generator Losses

Calculating the generator losses will look somewhat similar to calculating the discriminator loss; there will still be steps in which you generate fake images that look like they belong to the set of $X$ images but are based on real images in set $Y$, and vice versa. You'll compute the "real loss" on those generated images by looking at the output of the discriminator as it's applied to these fake images; this time, your generator aims to make the discriminator classify these fake images as real images.

Cycle Consistency Loss

In addition to the adversarial losses, the generator loss terms will also include the cycle consistency loss. This loss is a measure of how good a reconstructed image is, when compared to an original image.

Say you have a fake, generated image, x_hat, and a real image, y. You can get a reconstructed y_hat by applying G_XtoY(x_hat) = y_hat and then check to see if this reconstruction y_hat and the orginal image y match. For this, we recommed calculating the L1 loss, which is an absolute difference, between reconstructed and real images. You may also choose to multiply this loss by some weight value lambda_weight to convey its importance.

The total generator loss will be the sum of the generator losses and the forward and backward cycle consistency losses.


Define Loss Functions

To help us calculate the discriminator and gnerator losses during training, let's define some helpful loss functions. Here, we'll define three.

  1. real_mse_loss that looks at the output of a discriminator and returns the error based on how close that output is to being classified as real. This should be a mean squared error.
  2. fake_mse_loss that looks at the output of a discriminator and returns the error based on how close that output is to being classified as fake. This should be a mean squared error.
  3. cycle_consistency_loss that looks at a set of real image and a set of reconstructed/generated images, and returns the mean absolute error between them. This has a lambda_weight parameter that will weight the mean absolute error in a batch.

It's recommended that you take a look at the original, CycleGAN paper to get a starting value for lambda_weight.

In [18]:
def real_mse_loss(D_out):
    # how close is the produced output from being "real"?
    return torch.mean((D_out-1)**2)

def fake_mse_loss(D_out):
    # how close is the produced output from being "false"?
    return torch.mean(D_out**2)

def cycle_consistency_loss(real_im, reconstructed_im, lambda_weight):
    # calculate reconstruction loss 
    # return weighted loss
    return torch.mean(torch.abs(real_im - reconstructed_im))*lambda_weight
    

Define the Optimizers

Next, let's define how this model will update its weights. This, like the GANs you may have seen before, uses Adam optimizers for the discriminator and generator. It's again recommended that you take a look at the original, CycleGAN paper to get starting hyperparameter values.

In [19]:
import torch.optim as optim

# hyperparams for Adam optimizers
lr= 2e-4
beta1= 0.5
beta2= 0.999

g_params = list(G_XtoY.parameters()) + list(G_YtoX.parameters())  # Get generator parameters

# Create optimizers for the generators and discriminators
g_optimizer = optim.Adam(g_params, lr, [beta1, beta2])
d_x_optimizer = optim.Adam(D_X.parameters(), lr, [beta1, beta2])
d_y_optimizer = optim.Adam(D_Y.parameters(), lr, [beta1, beta2])

Training a CycleGAN

When a CycleGAN trains, and sees one batch of real images from set $X$ and $Y$, it trains by performing the following steps:

Training the Discriminators

  1. Compute the discriminator $D_X$ loss on real images
  2. Generate fake images that look like domain $X$ based on real images in domain $Y$
  3. Compute the fake loss for $D_X$
  4. Compute the total loss and perform backpropagation and $D_X$ optimization
  5. Repeat steps 1-4 only with $D_Y$ and your domains switched!

Training the Generators

  1. Generate fake images that look like domain $X$ based on real images in domain $Y$
  2. Compute the generator loss based on how $D_X$ responds to fake $X$
  3. Generate reconstructed $\hat{Y}$ images based on the fake $X$ images generated in step 1
  4. Compute the cycle consistency loss by comparing the reconstructions with real $Y$ images
  5. Repeat steps 1-4 only swapping domains
  6. Add up all the generator and reconstruction losses and perform backpropagation + optimization

Saving Your Progress

A CycleGAN repeats its training process, alternating between training the discriminators and the generators, for a specified number of training iterations. You've been given code that will save some example generated images that the CycleGAN has learned to generate after a certain number of training iterations. Along with looking at the losses, these example generations should give you an idea of how well your network has trained.

Below, you may choose to keep all default parameters; your only task is to calculate the appropriate losses and complete the training cycle.

In [20]:
# import save code
from helpers import save_samples, checkpoint
In [21]:
# train the network
def training_loop(dataloader_X, dataloader_Y, test_dataloader_X, test_dataloader_Y, 
                  n_epochs=1000):
    
    print_every=10
    
    # keep track of losses over time
    losses = []

    test_iter_X = iter(test_dataloader_X)
    test_iter_Y = iter(test_dataloader_Y)

    # Get some fixed data from domains X and Y for sampling. These are images that are held
    # constant throughout training, that allow us to inspect the model's performance.
    fixed_X = test_iter_X.next()[0]
    fixed_Y = test_iter_Y.next()[0]
    fixed_X = scale(fixed_X) # make sure to scale to a range -1 to 1
    fixed_Y = scale(fixed_Y)

    # batches per epoch
    iter_X = iter(dataloader_X)
    iter_Y = iter(dataloader_Y)
    batches_per_epoch = min(len(iter_X), len(iter_Y))

    for epoch in range(1, n_epochs+1):

        # Reset iterators for each epoch
        if epoch % batches_per_epoch == 0:
            iter_X = iter(dataloader_X)
            iter_Y = iter(dataloader_Y)

        images_X, _ = iter_X.next()
        images_X = scale(images_X) # make sure to scale to a range -1 to 1

        images_Y, _ = iter_Y.next()
        images_Y = scale(images_Y)
        
        # move images to GPU if available (otherwise stay on CPU)
        device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
        images_X = images_X.to(device)
        images_Y = images_Y.to(device)


        # ============================================
        #            TRAIN THE DISCRIMINATORS
        # ============================================

        ##   First: D_X, real and fake loss components   ##
        d_x_optimizer.zero_grad()
        
        # 1. Compute the discriminator losses on real images
        out_x = D_X(images_X)
        D_X_real_loss = real_mse_loss(out_x)
        
        # 2. Generate fake images that look like domain X based on real images in domain Y
        fake_X = G_YtoX(images_Y)
        
        # 3. Compute the fake loss for D_X
        out_x = D_X(fake_X)
        D_X_fake_loss = fake_mse_loss(out_x)
        # 4. Compute the total loss and perform backprop
        d_x_loss = D_X_real_loss + D_X_fake_loss
        d_x_loss.backward()
        d_x_optimizer.step()
        
        ##   Second: D_Y, real and fake loss components   ##
        
         # Train with real images
        d_y_optimizer.zero_grad()
        # 1. Compute the discriminator losses on real images
        out_y = D_Y(images_Y)
        D_Y_real_loss = real_mse_loss(out_y)
         # Train with fake images

        # 2. Generate fake images that look like domain Y based on real images in domain X
        
        fake_Y = G_XtoY(images_X)
        
        out_y = D_Y(fake_Y)
        D_Y_fake_loss = fake_mse_loss(out_y)
        
        # 4. Compute the total loss and perform backprop
        
        d_y_loss = D_Y_real_loss + D_Y_fake_loss
        d_y_loss.backward()
        d_y_optimizer.step()
        


        # =========================================
        #            TRAIN THE GENERATORS
        # =========================================

        ##    First: generate fake X images and reconstructed Y images    ##
        g_optimizer.zero_grad()
        # 1. Generate fake images that look like domain X based on real images in domain Y
        fake_X = G_YtoX(images_Y)
        
        # 2. Compute the generator loss based on domain X
        out_x = D_X(fake_X)
        g_YtoX_loss = real_mse_loss(out_x)
        
        # 3. Create a reconstructed y
        # 4. Compute the cycle consistency loss (the reconstruction loss)
        reconstructed_Y = G_XtoY(fake_X)
        reconstructed_y_loss = cycle_consistency_loss(images_Y, reconstructed_Y, lambda_weight=10)

        ##    Second: generate fake Y images and reconstructed X images    ##
        
        fake_Y = G_XtoY(images_X)
        
         # 2. Compute the generator loss based on domain Y
        out_y = D_Y(fake_Y)
        g_XtoY_loss = real_mse_loss(out_y)
        
        # 3. Create a reconstructed x
        # 4. Compute the cycle consistency loss (the reconstruction loss)
        reconstructed_X = G_YtoX(fake_Y)
        reconstructed_x_loss = cycle_consistency_loss(images_X, reconstructed_X, lambda_weight=10)
        # 5. Add up all generator and reconstructed losses and perform backprop
        g_total_loss = reconstructed_x_loss + reconstructed_y_loss + g_YtoX_loss + g_XtoY_loss
        g_total_loss.backward()
        g_optimizer.step()

        
        # Print the log info
        if epoch % print_every == 0:
            # append real and fake discriminator losses and the generator loss
            losses.append((d_x_loss.item(), d_y_loss.item(), g_total_loss.item()))
            print('Epoch [{:5d}/{:5d}] | d_X_loss: {:6.4f} | d_Y_loss: {:6.4f} | g_total_loss: {:6.4f}'.format(
                    epoch, n_epochs, d_x_loss.item(), d_y_loss.item(), g_total_loss.item()))

            
        sample_every=100
        # Save the generated samples
        if epoch % sample_every == 0:
            G_YtoX.eval() # set generators to eval mode for sample generation
            G_XtoY.eval()
            save_samples(epoch, fixed_Y, fixed_X, G_YtoX, G_XtoY, batch_size=16)
            G_YtoX.train()
            G_XtoY.train()

        # uncomment these lines, if you want to save your model
#         checkpoint_every=1000
#         # Save the model parameters
#         if epoch % checkpoint_every == 0:
#             checkpoint(epoch, G_XtoY, G_YtoX, D_X, D_Y)

    return losses
In [22]:
n_epochs = 5000 # keep this small when testing if a model first works, then increase it to >=1000

losses = training_loop(dataloader_X, dataloader_Y, test_dataloader_X, test_dataloader_Y, n_epochs=n_epochs)
Epoch [   10/ 5000] | d_X_loss: 0.4363 | d_Y_loss: 0.4269 | g_total_loss: 10.0649
Epoch [   20/ 5000] | d_X_loss: 0.3631 | d_Y_loss: 0.1990 | g_total_loss: 9.1509
Epoch [   30/ 5000] | d_X_loss: 0.4507 | d_Y_loss: 0.2806 | g_total_loss: 8.6458
Epoch [   40/ 5000] | d_X_loss: 0.2989 | d_Y_loss: 0.5651 | g_total_loss: 8.0889
Epoch [   50/ 5000] | d_X_loss: 0.3284 | d_Y_loss: 0.2966 | g_total_loss: 7.7060
Epoch [   60/ 5000] | d_X_loss: 0.3832 | d_Y_loss: 0.3647 | g_total_loss: 6.7769
Epoch [   70/ 5000] | d_X_loss: 0.4445 | d_Y_loss: 0.2309 | g_total_loss: 7.2262
Epoch [   80/ 5000] | d_X_loss: 0.3913 | d_Y_loss: 0.3640 | g_total_loss: 6.8049
Epoch [   90/ 5000] | d_X_loss: 0.3470 | d_Y_loss: 0.3828 | g_total_loss: 6.3074
Epoch [  100/ 5000] | d_X_loss: 0.3781 | d_Y_loss: 0.3596 | g_total_loss: 6.6701
Saved samples_cyclegan/sample-000100-X-Y.png
Saved samples_cyclegan/sample-000100-Y-X.png
Epoch [  110/ 5000] | d_X_loss: 0.3262 | d_Y_loss: 0.4569 | g_total_loss: 7.6985
Epoch [  120/ 5000] | d_X_loss: 0.3256 | d_Y_loss: 0.2482 | g_total_loss: 6.1129
Epoch [  130/ 5000] | d_X_loss: 0.3317 | d_Y_loss: 0.3079 | g_total_loss: 6.1054
Epoch [  140/ 5000] | d_X_loss: 0.5459 | d_Y_loss: 0.3757 | g_total_loss: 7.0279
Epoch [  150/ 5000] | d_X_loss: 0.3335 | d_Y_loss: 0.4006 | g_total_loss: 5.7659
Epoch [  160/ 5000] | d_X_loss: 0.4907 | d_Y_loss: 0.3902 | g_total_loss: 6.2596
Epoch [  170/ 5000] | d_X_loss: 0.3642 | d_Y_loss: 0.4025 | g_total_loss: 5.8277
Epoch [  180/ 5000] | d_X_loss: 0.3231 | d_Y_loss: 0.3123 | g_total_loss: 7.0552
Epoch [  190/ 5000] | d_X_loss: 0.4082 | d_Y_loss: 0.3082 | g_total_loss: 6.4910
Epoch [  200/ 5000] | d_X_loss: 0.3171 | d_Y_loss: 0.3032 | g_total_loss: 6.3395
Saved samples_cyclegan/sample-000200-X-Y.png
Saved samples_cyclegan/sample-000200-Y-X.png
Epoch [  210/ 5000] | d_X_loss: 0.4246 | d_Y_loss: 0.3020 | g_total_loss: 5.8224
Epoch [  220/ 5000] | d_X_loss: 0.2649 | d_Y_loss: 0.3033 | g_total_loss: 6.2974
Epoch [  230/ 5000] | d_X_loss: 0.3161 | d_Y_loss: 0.3070 | g_total_loss: 6.2058
Epoch [  240/ 5000] | d_X_loss: 0.4769 | d_Y_loss: 0.5765 | g_total_loss: 6.7250
Epoch [  250/ 5000] | d_X_loss: 0.2349 | d_Y_loss: 0.3407 | g_total_loss: 6.1540
Epoch [  260/ 5000] | d_X_loss: 0.3456 | d_Y_loss: 0.4284 | g_total_loss: 5.4573
Epoch [  270/ 5000] | d_X_loss: 0.5645 | d_Y_loss: 0.5648 | g_total_loss: 5.6417
Epoch [  280/ 5000] | d_X_loss: 0.3897 | d_Y_loss: 0.6821 | g_total_loss: 6.5787
Epoch [  290/ 5000] | d_X_loss: 0.3196 | d_Y_loss: 0.3187 | g_total_loss: 5.5676
Epoch [  300/ 5000] | d_X_loss: 0.4863 | d_Y_loss: 0.3342 | g_total_loss: 5.2348
Saved samples_cyclegan/sample-000300-X-Y.png
Saved samples_cyclegan/sample-000300-Y-X.png
Epoch [  310/ 5000] | d_X_loss: 0.3700 | d_Y_loss: 0.4568 | g_total_loss: 5.4086
Epoch [  320/ 5000] | d_X_loss: 0.4162 | d_Y_loss: 0.4367 | g_total_loss: 5.1478
Epoch [  330/ 5000] | d_X_loss: 0.3937 | d_Y_loss: 0.3675 | g_total_loss: 5.6469
Epoch [  340/ 5000] | d_X_loss: 0.5283 | d_Y_loss: 0.3074 | g_total_loss: 4.7478
Epoch [  350/ 5000] | d_X_loss: 0.3339 | d_Y_loss: 0.5536 | g_total_loss: 6.4114
Epoch [  360/ 5000] | d_X_loss: 0.3810 | d_Y_loss: 0.4285 | g_total_loss: 5.6878
Epoch [  370/ 5000] | d_X_loss: 0.3649 | d_Y_loss: 0.4069 | g_total_loss: 5.6941
Epoch [  380/ 5000] | d_X_loss: 0.3877 | d_Y_loss: 0.3831 | g_total_loss: 5.5527
Epoch [  390/ 5000] | d_X_loss: 0.3682 | d_Y_loss: 0.3094 | g_total_loss: 4.8064
Epoch [  400/ 5000] | d_X_loss: 0.3248 | d_Y_loss: 0.4144 | g_total_loss: 5.7937
Saved samples_cyclegan/sample-000400-X-Y.png
Saved samples_cyclegan/sample-000400-Y-X.png
Epoch [  410/ 5000] | d_X_loss: 0.4469 | d_Y_loss: 0.3094 | g_total_loss: 5.7611
Epoch [  420/ 5000] | d_X_loss: 0.2980 | d_Y_loss: 0.2723 | g_total_loss: 5.0700
Epoch [  430/ 5000] | d_X_loss: 0.4138 | d_Y_loss: 0.4280 | g_total_loss: 4.9360
Epoch [  440/ 5000] | d_X_loss: 0.3283 | d_Y_loss: 0.3722 | g_total_loss: 5.3418
Epoch [  450/ 5000] | d_X_loss: 0.3713 | d_Y_loss: 0.4272 | g_total_loss: 5.5315
Epoch [  460/ 5000] | d_X_loss: 0.3059 | d_Y_loss: 0.3218 | g_total_loss: 5.1958
Epoch [  470/ 5000] | d_X_loss: 0.5041 | d_Y_loss: 0.3764 | g_total_loss: 4.6599
Epoch [  480/ 5000] | d_X_loss: 0.1942 | d_Y_loss: 0.3046 | g_total_loss: 6.5480
Epoch [  490/ 5000] | d_X_loss: 0.4004 | d_Y_loss: 0.2649 | g_total_loss: 6.2523
Epoch [  500/ 5000] | d_X_loss: 0.3564 | d_Y_loss: 0.4003 | g_total_loss: 4.8588
Saved samples_cyclegan/sample-000500-X-Y.png
Saved samples_cyclegan/sample-000500-Y-X.png
Epoch [  510/ 5000] | d_X_loss: 0.3159 | d_Y_loss: 0.2376 | g_total_loss: 5.7904
Epoch [  520/ 5000] | d_X_loss: 0.3608 | d_Y_loss: 0.3946 | g_total_loss: 6.0105
Epoch [  530/ 5000] | d_X_loss: 0.3384 | d_Y_loss: 0.3094 | g_total_loss: 5.6203
Epoch [  540/ 5000] | d_X_loss: 0.3578 | d_Y_loss: 0.5173 | g_total_loss: 5.7726
Epoch [  550/ 5000] | d_X_loss: 0.2752 | d_Y_loss: 0.4525 | g_total_loss: 5.6762
Epoch [  560/ 5000] | d_X_loss: 0.3832 | d_Y_loss: 0.4923 | g_total_loss: 5.3432
Epoch [  570/ 5000] | d_X_loss: 0.4252 | d_Y_loss: 0.4363 | g_total_loss: 5.1508
Epoch [  580/ 5000] | d_X_loss: 0.3998 | d_Y_loss: 0.4141 | g_total_loss: 5.5036
Epoch [  590/ 5000] | d_X_loss: 0.4576 | d_Y_loss: 0.4338 | g_total_loss: 5.1880
Epoch [  600/ 5000] | d_X_loss: 0.3309 | d_Y_loss: 0.3183 | g_total_loss: 5.3650
Saved samples_cyclegan/sample-000600-X-Y.png
Saved samples_cyclegan/sample-000600-Y-X.png
Epoch [  610/ 5000] | d_X_loss: 0.5620 | d_Y_loss: 0.4099 | g_total_loss: 6.3626
Epoch [  620/ 5000] | d_X_loss: 0.2885 | d_Y_loss: 0.2587 | g_total_loss: 5.9163
Epoch [  630/ 5000] | d_X_loss: 0.2878 | d_Y_loss: 0.3122 | g_total_loss: 5.7914
Epoch [  640/ 5000] | d_X_loss: 0.3535 | d_Y_loss: 0.4338 | g_total_loss: 5.2956
Epoch [  650/ 5000] | d_X_loss: 0.3029 | d_Y_loss: 0.4517 | g_total_loss: 5.6708
Epoch [  660/ 5000] | d_X_loss: 0.3551 | d_Y_loss: 0.3982 | g_total_loss: 5.1226
Epoch [  670/ 5000] | d_X_loss: 0.2604 | d_Y_loss: 0.3434 | g_total_loss: 8.2370
Epoch [  680/ 5000] | d_X_loss: 0.5789 | d_Y_loss: 0.3650 | g_total_loss: 5.1068
Epoch [  690/ 5000] | d_X_loss: 0.3333 | d_Y_loss: 0.5233 | g_total_loss: 5.9945
Epoch [  700/ 5000] | d_X_loss: 0.3180 | d_Y_loss: 0.3242 | g_total_loss: 5.5835
Saved samples_cyclegan/sample-000700-X-Y.png
Saved samples_cyclegan/sample-000700-Y-X.png
Epoch [  710/ 5000] | d_X_loss: 0.5040 | d_Y_loss: 0.2943 | g_total_loss: 5.0834
Epoch [  720/ 5000] | d_X_loss: 0.3115 | d_Y_loss: 0.5000 | g_total_loss: 5.0041
Epoch [  730/ 5000] | d_X_loss: 0.2382 | d_Y_loss: 0.2493 | g_total_loss: 6.0201
Epoch [  740/ 5000] | d_X_loss: 0.3099 | d_Y_loss: 0.2029 | g_total_loss: 5.6114
Epoch [  750/ 5000] | d_X_loss: 0.2794 | d_Y_loss: 0.3518 | g_total_loss: 5.1209
Epoch [  760/ 5000] | d_X_loss: 0.3225 | d_Y_loss: 0.3984 | g_total_loss: 6.9366
Epoch [  770/ 5000] | d_X_loss: 0.3712 | d_Y_loss: 0.4410 | g_total_loss: 6.1534
Epoch [  780/ 5000] | d_X_loss: 0.3278 | d_Y_loss: 0.3263 | g_total_loss: 5.0133
Epoch [  790/ 5000] | d_X_loss: 0.2994 | d_Y_loss: 0.2808 | g_total_loss: 5.6036
Epoch [  800/ 5000] | d_X_loss: 0.3317 | d_Y_loss: 0.2621 | g_total_loss: 5.1106
Saved samples_cyclegan/sample-000800-X-Y.png
Saved samples_cyclegan/sample-000800-Y-X.png
Epoch [  810/ 5000] | d_X_loss: 0.4814 | d_Y_loss: 0.2182 | g_total_loss: 5.1827
Epoch [  820/ 5000] | d_X_loss: 0.3126 | d_Y_loss: 0.3288 | g_total_loss: 5.1626
Epoch [  830/ 5000] | d_X_loss: 0.3037 | d_Y_loss: 0.5040 | g_total_loss: 5.9586
Epoch [  840/ 5000] | d_X_loss: 0.2800 | d_Y_loss: 0.3383 | g_total_loss: 4.6702
Epoch [  850/ 5000] | d_X_loss: 0.2501 | d_Y_loss: 0.2680 | g_total_loss: 4.5250
Epoch [  860/ 5000] | d_X_loss: 0.3466 | d_Y_loss: 0.2653 | g_total_loss: 5.3450
Epoch [  870/ 5000] | d_X_loss: 0.3769 | d_Y_loss: 0.2996 | g_total_loss: 4.2532
Epoch [  880/ 5000] | d_X_loss: 0.4545 | d_Y_loss: 0.2391 | g_total_loss: 5.4241
Epoch [  890/ 5000] | d_X_loss: 0.3198 | d_Y_loss: 0.1979 | g_total_loss: 5.2305
Epoch [  900/ 5000] | d_X_loss: 0.3728 | d_Y_loss: 0.2012 | g_total_loss: 5.9348
Saved samples_cyclegan/sample-000900-X-Y.png
Saved samples_cyclegan/sample-000900-Y-X.png
Epoch [  910/ 5000] | d_X_loss: 0.3141 | d_Y_loss: 0.4480 | g_total_loss: 4.7021
Epoch [  920/ 5000] | d_X_loss: 0.3590 | d_Y_loss: 0.4188 | g_total_loss: 5.3822
Epoch [  930/ 5000] | d_X_loss: 0.3887 | d_Y_loss: 0.3723 | g_total_loss: 5.4081
Epoch [  940/ 5000] | d_X_loss: 0.4492 | d_Y_loss: 0.4183 | g_total_loss: 4.7916
Epoch [  950/ 5000] | d_X_loss: 0.3765 | d_Y_loss: 0.3035 | g_total_loss: 5.1101
Epoch [  960/ 5000] | d_X_loss: 0.2951 | d_Y_loss: 0.2393 | g_total_loss: 5.3662
Epoch [  970/ 5000] | d_X_loss: 0.3547 | d_Y_loss: 0.3363 | g_total_loss: 6.1202
Epoch [  980/ 5000] | d_X_loss: 0.3006 | d_Y_loss: 0.3276 | g_total_loss: 5.5290
Epoch [  990/ 5000] | d_X_loss: 0.2184 | d_Y_loss: 0.2746 | g_total_loss: 4.9296
Epoch [ 1000/ 5000] | d_X_loss: 0.3008 | d_Y_loss: 0.2705 | g_total_loss: 6.1596
Saved samples_cyclegan/sample-001000-X-Y.png
Saved samples_cyclegan/sample-001000-Y-X.png
Epoch [ 1010/ 5000] | d_X_loss: 0.2688 | d_Y_loss: 0.3197 | g_total_loss: 5.5550
Epoch [ 1020/ 5000] | d_X_loss: 0.3289 | d_Y_loss: 0.7193 | g_total_loss: 5.4947
Epoch [ 1030/ 5000] | d_X_loss: 0.2250 | d_Y_loss: 0.2943 | g_total_loss: 6.3624
Epoch [ 1040/ 5000] | d_X_loss: 0.2731 | d_Y_loss: 0.5006 | g_total_loss: 4.8554
Epoch [ 1050/ 5000] | d_X_loss: 0.2818 | d_Y_loss: 0.1999 | g_total_loss: 5.5103
Epoch [ 1060/ 5000] | d_X_loss: 0.3753 | d_Y_loss: 0.4729 | g_total_loss: 4.8881
Epoch [ 1070/ 5000] | d_X_loss: 0.3604 | d_Y_loss: 0.4250 | g_total_loss: 4.6478
Epoch [ 1080/ 5000] | d_X_loss: 0.3058 | d_Y_loss: 0.1975 | g_total_loss: 5.5573
Epoch [ 1090/ 5000] | d_X_loss: 0.2648 | d_Y_loss: 0.4158 | g_total_loss: 5.2898
Epoch [ 1100/ 5000] | d_X_loss: 0.3769 | d_Y_loss: 0.2489 | g_total_loss: 4.4890
Saved samples_cyclegan/sample-001100-X-Y.png
Saved samples_cyclegan/sample-001100-Y-X.png
Epoch [ 1110/ 5000] | d_X_loss: 0.3317 | d_Y_loss: 0.2211 | g_total_loss: 5.3336
Epoch [ 1120/ 5000] | d_X_loss: 0.3685 | d_Y_loss: 0.3240 | g_total_loss: 6.2373
Epoch [ 1130/ 5000] | d_X_loss: 0.3148 | d_Y_loss: 0.4796 | g_total_loss: 5.1453
Epoch [ 1140/ 5000] | d_X_loss: 0.3252 | d_Y_loss: 0.3143 | g_total_loss: 5.5390
Epoch [ 1150/ 5000] | d_X_loss: 0.5360 | d_Y_loss: 0.4381 | g_total_loss: 4.8634
Epoch [ 1160/ 5000] | d_X_loss: 0.3082 | d_Y_loss: 0.2671 | g_total_loss: 5.9038
Epoch [ 1170/ 5000] | d_X_loss: 0.3709 | d_Y_loss: 0.1897 | g_total_loss: 5.2637
Epoch [ 1180/ 5000] | d_X_loss: 0.4827 | d_Y_loss: 0.3284 | g_total_loss: 5.9968
Epoch [ 1190/ 5000] | d_X_loss: 0.2169 | d_Y_loss: 0.3268 | g_total_loss: 5.3604
Epoch [ 1200/ 5000] | d_X_loss: 0.3106 | d_Y_loss: 0.2746 | g_total_loss: 5.4270
Saved samples_cyclegan/sample-001200-X-Y.png
Saved samples_cyclegan/sample-001200-Y-X.png
Epoch [ 1210/ 5000] | d_X_loss: 0.3074 | d_Y_loss: 0.2253 | g_total_loss: 6.1694
Epoch [ 1220/ 5000] | d_X_loss: 0.3301 | d_Y_loss: 0.3535 | g_total_loss: 5.2693
Epoch [ 1230/ 5000] | d_X_loss: 0.2200 | d_Y_loss: 0.3929 | g_total_loss: 5.6393
Epoch [ 1240/ 5000] | d_X_loss: 0.5264 | d_Y_loss: 0.3323 | g_total_loss: 6.2512
Epoch [ 1250/ 5000] | d_X_loss: 0.2089 | d_Y_loss: 0.4355 | g_total_loss: 5.0174
Epoch [ 1260/ 5000] | d_X_loss: 0.3107 | d_Y_loss: 0.3327 | g_total_loss: 5.3240
Epoch [ 1270/ 5000] | d_X_loss: 0.3316 | d_Y_loss: 0.4246 | g_total_loss: 5.0017
Epoch [ 1280/ 5000] | d_X_loss: 0.4609 | d_Y_loss: 0.3141 | g_total_loss: 4.9937
Epoch [ 1290/ 5000] | d_X_loss: 0.2066 | d_Y_loss: 0.3943 | g_total_loss: 4.9025
Epoch [ 1300/ 5000] | d_X_loss: 0.3205 | d_Y_loss: 0.2267 | g_total_loss: 5.8254
Saved samples_cyclegan/sample-001300-X-Y.png
Saved samples_cyclegan/sample-001300-Y-X.png
Epoch [ 1310/ 5000] | d_X_loss: 0.2857 | d_Y_loss: 0.4261 | g_total_loss: 4.0618
Epoch [ 1320/ 5000] | d_X_loss: 0.3801 | d_Y_loss: 0.2623 | g_total_loss: 4.6613
Epoch [ 1330/ 5000] | d_X_loss: 0.3272 | d_Y_loss: 0.2924 | g_total_loss: 5.0475
Epoch [ 1340/ 5000] | d_X_loss: 0.2797 | d_Y_loss: 0.3620 | g_total_loss: 6.2065
Epoch [ 1350/ 5000] | d_X_loss: 0.1735 | d_Y_loss: 0.5060 | g_total_loss: 5.6843
Epoch [ 1360/ 5000] | d_X_loss: 0.3293 | d_Y_loss: 0.2213 | g_total_loss: 6.1578
Epoch [ 1370/ 5000] | d_X_loss: 0.3375 | d_Y_loss: 0.3180 | g_total_loss: 4.2590
Epoch [ 1380/ 5000] | d_X_loss: 0.3201 | d_Y_loss: 0.1928 | g_total_loss: 4.9678
Epoch [ 1390/ 5000] | d_X_loss: 0.2245 | d_Y_loss: 0.3042 | g_total_loss: 5.1208
Epoch [ 1400/ 5000] | d_X_loss: 0.3909 | d_Y_loss: 0.2157 | g_total_loss: 4.9948
Saved samples_cyclegan/sample-001400-X-Y.png
Saved samples_cyclegan/sample-001400-Y-X.png
Epoch [ 1410/ 5000] | d_X_loss: 0.2128 | d_Y_loss: 0.2135 | g_total_loss: 5.3992
Epoch [ 1420/ 5000] | d_X_loss: 0.4079 | d_Y_loss: 0.3749 | g_total_loss: 4.4893
Epoch [ 1430/ 5000] | d_X_loss: 0.4152 | d_Y_loss: 0.2143 | g_total_loss: 4.5604
Epoch [ 1440/ 5000] | d_X_loss: 0.5003 | d_Y_loss: 0.2666 | g_total_loss: 6.0269
Epoch [ 1450/ 5000] | d_X_loss: 0.2361 | d_Y_loss: 0.5153 | g_total_loss: 5.5275
Epoch [ 1460/ 5000] | d_X_loss: 0.2824 | d_Y_loss: 0.5007 | g_total_loss: 4.3868
Epoch [ 1470/ 5000] | d_X_loss: 0.2416 | d_Y_loss: 0.2420 | g_total_loss: 5.3021
Epoch [ 1480/ 5000] | d_X_loss: 0.3894 | d_Y_loss: 0.2653 | g_total_loss: 5.5651
Epoch [ 1490/ 5000] | d_X_loss: 0.2345 | d_Y_loss: 0.1676 | g_total_loss: 5.6493
Epoch [ 1500/ 5000] | d_X_loss: 0.3753 | d_Y_loss: 0.2725 | g_total_loss: 4.4149
Saved samples_cyclegan/sample-001500-X-Y.png
Saved samples_cyclegan/sample-001500-Y-X.png
Epoch [ 1510/ 5000] | d_X_loss: 0.2611 | d_Y_loss: 0.4076 | g_total_loss: 5.4091
Epoch [ 1520/ 5000] | d_X_loss: 0.3287 | d_Y_loss: 0.4225 | g_total_loss: 4.3585
Epoch [ 1530/ 5000] | d_X_loss: 0.2972 | d_Y_loss: 0.2513 | g_total_loss: 5.2824
Epoch [ 1540/ 5000] | d_X_loss: 0.1867 | d_Y_loss: 0.3592 | g_total_loss: 4.3147
Epoch [ 1550/ 5000] | d_X_loss: 0.3039 | d_Y_loss: 0.3339 | g_total_loss: 4.5177
Epoch [ 1560/ 5000] | d_X_loss: 0.2561 | d_Y_loss: 0.1413 | g_total_loss: 5.8823
Epoch [ 1570/ 5000] | d_X_loss: 0.3828 | d_Y_loss: 0.3024 | g_total_loss: 4.6296
Epoch [ 1580/ 5000] | d_X_loss: 0.2432 | d_Y_loss: 0.2712 | g_total_loss: 5.3087
Epoch [ 1590/ 5000] | d_X_loss: 0.2040 | d_Y_loss: 0.1847 | g_total_loss: 5.8171
Epoch [ 1600/ 5000] | d_X_loss: 0.2651 | d_Y_loss: 0.1833 | g_total_loss: 4.9499
Saved samples_cyclegan/sample-001600-X-Y.png
Saved samples_cyclegan/sample-001600-Y-X.png
Epoch [ 1610/ 5000] | d_X_loss: 0.2814 | d_Y_loss: 0.3384 | g_total_loss: 4.6809
Epoch [ 1620/ 5000] | d_X_loss: 0.2489 | d_Y_loss: 0.3163 | g_total_loss: 5.5502
Epoch [ 1630/ 5000] | d_X_loss: 0.3853 | d_Y_loss: 0.1521 | g_total_loss: 4.6825
Epoch [ 1640/ 5000] | d_X_loss: 0.3286 | d_Y_loss: 0.3116 | g_total_loss: 6.6254
Epoch [ 1650/ 5000] | d_X_loss: 0.2525 | d_Y_loss: 0.2479 | g_total_loss: 4.7956
Epoch [ 1660/ 5000] | d_X_loss: 0.2526 | d_Y_loss: 0.2390 | g_total_loss: 5.8665
Epoch [ 1670/ 5000] | d_X_loss: 0.2674 | d_Y_loss: 0.2776 | g_total_loss: 5.6565
Epoch [ 1680/ 5000] | d_X_loss: 0.2440 | d_Y_loss: 0.2949 | g_total_loss: 5.1589
Epoch [ 1690/ 5000] | d_X_loss: 0.2274 | d_Y_loss: 0.2269 | g_total_loss: 4.8075
Epoch [ 1700/ 5000] | d_X_loss: 0.2324 | d_Y_loss: 0.2612 | g_total_loss: 5.0246
Saved samples_cyclegan/sample-001700-X-Y.png
Saved samples_cyclegan/sample-001700-Y-X.png
Epoch [ 1710/ 5000] | d_X_loss: 0.2642 | d_Y_loss: 0.4109 | g_total_loss: 5.5734
Epoch [ 1720/ 5000] | d_X_loss: 0.2062 | d_Y_loss: 0.3023 | g_total_loss: 5.2835
Epoch [ 1730/ 5000] | d_X_loss: 0.2894 | d_Y_loss: 0.3440 | g_total_loss: 5.0244
Epoch [ 1740/ 5000] | d_X_loss: 0.3119 | d_Y_loss: 0.3435 | g_total_loss: 5.2838
Epoch [ 1750/ 5000] | d_X_loss: 0.4067 | d_Y_loss: 0.3202 | g_total_loss: 4.3872
Epoch [ 1760/ 5000] | d_X_loss: 0.2316 | d_Y_loss: 0.2275 | g_total_loss: 4.9832
Epoch [ 1770/ 5000] | d_X_loss: 0.3988 | d_Y_loss: 0.1605 | g_total_loss: 5.0788
Epoch [ 1780/ 5000] | d_X_loss: 0.2087 | d_Y_loss: 0.1123 | g_total_loss: 5.3348
Epoch [ 1790/ 5000] | d_X_loss: 0.2953 | d_Y_loss: 0.2819 | g_total_loss: 4.6944
Epoch [ 1800/ 5000] | d_X_loss: 0.3036 | d_Y_loss: 0.2348 | g_total_loss: 4.7599
Saved samples_cyclegan/sample-001800-X-Y.png
Saved samples_cyclegan/sample-001800-Y-X.png
Epoch [ 1810/ 5000] | d_X_loss: 0.1696 | d_Y_loss: 0.2025 | g_total_loss: 5.1825
Epoch [ 1820/ 5000] | d_X_loss: 0.3328 | d_Y_loss: 0.2007 | g_total_loss: 5.1438
Epoch [ 1830/ 5000] | d_X_loss: 0.2177 | d_Y_loss: 0.3760 | g_total_loss: 4.7383
Epoch [ 1840/ 5000] | d_X_loss: 0.2627 | d_Y_loss: 0.3762 | g_total_loss: 4.6878
Epoch [ 1850/ 5000] | d_X_loss: 0.2388 | d_Y_loss: 0.3873 | g_total_loss: 4.8788
Epoch [ 1860/ 5000] | d_X_loss: 0.1921 | d_Y_loss: 0.2420 | g_total_loss: 5.4217
Epoch [ 1870/ 5000] | d_X_loss: 0.2397 | d_Y_loss: 0.3863 | g_total_loss: 4.4766
Epoch [ 1880/ 5000] | d_X_loss: 0.2247 | d_Y_loss: 0.3032 | g_total_loss: 5.0299
Epoch [ 1890/ 5000] | d_X_loss: 0.2046 | d_Y_loss: 0.3482 | g_total_loss: 6.1956
Epoch [ 1900/ 5000] | d_X_loss: 0.2887 | d_Y_loss: 0.2201 | g_total_loss: 4.5585
Saved samples_cyclegan/sample-001900-X-Y.png
Saved samples_cyclegan/sample-001900-Y-X.png
Epoch [ 1910/ 5000] | d_X_loss: 0.2351 | d_Y_loss: 0.2303 | g_total_loss: 5.4536
Epoch [ 1920/ 5000] | d_X_loss: 0.1962 | d_Y_loss: 0.2127 | g_total_loss: 5.9225
Epoch [ 1930/ 5000] | d_X_loss: 0.3632 | d_Y_loss: 0.2249 | g_total_loss: 4.7540
Epoch [ 1940/ 5000] | d_X_loss: 0.4025 | d_Y_loss: 0.2563 | g_total_loss: 4.7737
Epoch [ 1950/ 5000] | d_X_loss: 0.3307 | d_Y_loss: 0.2004 | g_total_loss: 5.4694
Epoch [ 1960/ 5000] | d_X_loss: 0.1604 | d_Y_loss: 0.3167 | g_total_loss: 5.7367
Epoch [ 1970/ 5000] | d_X_loss: 0.2377 | d_Y_loss: 0.4331 | g_total_loss: 5.0685
Epoch [ 1980/ 5000] | d_X_loss: 0.2776 | d_Y_loss: 0.3718 | g_total_loss: 4.5900
Epoch [ 1990/ 5000] | d_X_loss: 0.2562 | d_Y_loss: 0.3152 | g_total_loss: 4.1719
Epoch [ 2000/ 5000] | d_X_loss: 0.3284 | d_Y_loss: 0.2703 | g_total_loss: 4.4757
Saved samples_cyclegan/sample-002000-X-Y.png
Saved samples_cyclegan/sample-002000-Y-X.png
Epoch [ 2010/ 5000] | d_X_loss: 0.2092 | d_Y_loss: 0.3102 | g_total_loss: 5.3039
Epoch [ 2020/ 5000] | d_X_loss: 0.1374 | d_Y_loss: 0.2886 | g_total_loss: 5.1205
Epoch [ 2030/ 5000] | d_X_loss: 0.2443 | d_Y_loss: 0.2168 | g_total_loss: 4.5784
Epoch [ 2040/ 5000] | d_X_loss: 0.3252 | d_Y_loss: 0.3342 | g_total_loss: 3.9617
Epoch [ 2050/ 5000] | d_X_loss: 0.1880 | d_Y_loss: 0.2487 | g_total_loss: 5.2165
Epoch [ 2060/ 5000] | d_X_loss: 0.2564 | d_Y_loss: 0.2220 | g_total_loss: 5.0384
Epoch [ 2070/ 5000] | d_X_loss: 0.3512 | d_Y_loss: 0.2765 | g_total_loss: 4.9105
Epoch [ 2080/ 5000] | d_X_loss: 0.3111 | d_Y_loss: 0.2140 | g_total_loss: 5.4311
Epoch [ 2090/ 5000] | d_X_loss: 0.4281 | d_Y_loss: 0.2079 | g_total_loss: 6.1148
Epoch [ 2100/ 5000] | d_X_loss: 0.2184 | d_Y_loss: 0.2548 | g_total_loss: 5.1768
Saved samples_cyclegan/sample-002100-X-Y.png
Saved samples_cyclegan/sample-002100-Y-X.png
Epoch [ 2110/ 5000] | d_X_loss: 0.2842 | d_Y_loss: 0.1846 | g_total_loss: 5.4816
Epoch [ 2120/ 5000] | d_X_loss: 0.3509 | d_Y_loss: 0.1766 | g_total_loss: 4.6185
Epoch [ 2130/ 5000] | d_X_loss: 0.2682 | d_Y_loss: 0.2041 | g_total_loss: 5.1331
Epoch [ 2140/ 5000] | d_X_loss: 0.2016 | d_Y_loss: 0.3074 | g_total_loss: 4.8110
Epoch [ 2150/ 5000] | d_X_loss: 0.1931 | d_Y_loss: 0.2458 | g_total_loss: 4.8329
Epoch [ 2160/ 5000] | d_X_loss: 0.2054 | d_Y_loss: 0.2191 | g_total_loss: 4.9233
Epoch [ 2170/ 5000] | d_X_loss: 0.3699 | d_Y_loss: 0.2257 | g_total_loss: 5.1121
Epoch [ 2180/ 5000] | d_X_loss: 0.1581 | d_Y_loss: 0.2786 | g_total_loss: 5.0615
Epoch [ 2190/ 5000] | d_X_loss: 0.3095 | d_Y_loss: 0.2294 | g_total_loss: 4.6546
Epoch [ 2200/ 5000] | d_X_loss: 0.4564 | d_Y_loss: 0.0997 | g_total_loss: 5.5364
Saved samples_cyclegan/sample-002200-X-Y.png
Saved samples_cyclegan/sample-002200-Y-X.png
Epoch [ 2210/ 5000] | d_X_loss: 0.1559 | d_Y_loss: 0.1510 | g_total_loss: 4.8733
Epoch [ 2220/ 5000] | d_X_loss: 0.5951 | d_Y_loss: 0.1018 | g_total_loss: 4.7357
Epoch [ 2230/ 5000] | d_X_loss: 0.2221 | d_Y_loss: 0.2990 | g_total_loss: 4.9391
Epoch [ 2240/ 5000] | d_X_loss: 0.1788 | d_Y_loss: 0.2139 | g_total_loss: 5.3113
Epoch [ 2250/ 5000] | d_X_loss: 0.3157 | d_Y_loss: 0.2119 | g_total_loss: 4.3410
Epoch [ 2260/ 5000] | d_X_loss: 0.1633 | d_Y_loss: 0.2093 | g_total_loss: 4.9913
Epoch [ 2270/ 5000] | d_X_loss: 0.1631 | d_Y_loss: 0.2765 | g_total_loss: 5.3473
Epoch [ 2280/ 5000] | d_X_loss: 0.3573 | d_Y_loss: 0.1644 | g_total_loss: 5.3206
Epoch [ 2290/ 5000] | d_X_loss: 0.2459 | d_Y_loss: 0.2431 | g_total_loss: 4.8990
Epoch [ 2300/ 5000] | d_X_loss: 0.3375 | d_Y_loss: 0.1908 | g_total_loss: 5.2504
Saved samples_cyclegan/sample-002300-X-Y.png
Saved samples_cyclegan/sample-002300-Y-X.png
Epoch [ 2310/ 5000] | d_X_loss: 0.1475 | d_Y_loss: 0.2486 | g_total_loss: 5.3788
Epoch [ 2320/ 5000] | d_X_loss: 0.1925 | d_Y_loss: 0.2319 | g_total_loss: 5.1875
Epoch [ 2330/ 5000] | d_X_loss: 0.1779 | d_Y_loss: 0.2226 | g_total_loss: 4.6775
Epoch [ 2340/ 5000] | d_X_loss: 0.4072 | d_Y_loss: 0.1425 | g_total_loss: 5.5527
Epoch [ 2350/ 5000] | d_X_loss: 0.2176 | d_Y_loss: 0.2943 | g_total_loss: 5.2394
Epoch [ 2360/ 5000] | d_X_loss: 0.1693 | d_Y_loss: 0.1993 | g_total_loss: 5.1666
Epoch [ 2370/ 5000] | d_X_loss: 0.2059 | d_Y_loss: 0.1334 | g_total_loss: 5.1381
Epoch [ 2380/ 5000] | d_X_loss: 0.2649 | d_Y_loss: 0.1257 | g_total_loss: 4.7188
Epoch [ 2390/ 5000] | d_X_loss: 0.2109 | d_Y_loss: 0.3942 | g_total_loss: 5.3321
Epoch [ 2400/ 5000] | d_X_loss: 0.2775 | d_Y_loss: 0.3149 | g_total_loss: 5.0683
Saved samples_cyclegan/sample-002400-X-Y.png
Saved samples_cyclegan/sample-002400-Y-X.png
Epoch [ 2410/ 5000] | d_X_loss: 0.2404 | d_Y_loss: 0.2172 | g_total_loss: 5.5590
Epoch [ 2420/ 5000] | d_X_loss: 0.1811 | d_Y_loss: 0.1590 | g_total_loss: 5.2859
Epoch [ 2430/ 5000] | d_X_loss: 0.3642 | d_Y_loss: 0.1966 | g_total_loss: 5.0019
Epoch [ 2440/ 5000] | d_X_loss: 0.2213 | d_Y_loss: 0.1615 | g_total_loss: 5.4114
Epoch [ 2450/ 5000] | d_X_loss: 0.1822 | d_Y_loss: 0.1416 | g_total_loss: 5.0389
Epoch [ 2460/ 5000] | d_X_loss: 0.2603 | d_Y_loss: 0.3313 | g_total_loss: 5.4762
Epoch [ 2470/ 5000] | d_X_loss: 0.2699 | d_Y_loss: 0.1567 | g_total_loss: 5.3407
Epoch [ 2480/ 5000] | d_X_loss: 0.2129 | d_Y_loss: 0.1519 | g_total_loss: 4.9560
Epoch [ 2490/ 5000] | d_X_loss: 0.1848 | d_Y_loss: 0.2384 | g_total_loss: 4.9839
Epoch [ 2500/ 5000] | d_X_loss: 0.1489 | d_Y_loss: 0.4980 | g_total_loss: 5.2422
Saved samples_cyclegan/sample-002500-X-Y.png
Saved samples_cyclegan/sample-002500-Y-X.png
Epoch [ 2510/ 5000] | d_X_loss: 0.1525 | d_Y_loss: 0.5648 | g_total_loss: 4.7669
Epoch [ 2520/ 5000] | d_X_loss: 0.2471 | d_Y_loss: 0.2244 | g_total_loss: 4.9981
Epoch [ 2530/ 5000] | d_X_loss: 0.1120 | d_Y_loss: 0.2085 | g_total_loss: 5.3037
Epoch [ 2540/ 5000] | d_X_loss: 0.2331 | d_Y_loss: 0.2545 | g_total_loss: 4.9827
Epoch [ 2550/ 5000] | d_X_loss: 0.2542 | d_Y_loss: 0.1993 | g_total_loss: 4.5938
Epoch [ 2560/ 5000] | d_X_loss: 0.1946 | d_Y_loss: 0.2521 | g_total_loss: 4.9186
Epoch [ 2570/ 5000] | d_X_loss: 0.0968 | d_Y_loss: 0.2097 | g_total_loss: 5.5712
Epoch [ 2580/ 5000] | d_X_loss: 0.2738 | d_Y_loss: 0.4126 | g_total_loss: 5.2709
Epoch [ 2590/ 5000] | d_X_loss: 0.2279 | d_Y_loss: 0.3337 | g_total_loss: 4.3868
Epoch [ 2600/ 5000] | d_X_loss: 0.2111 | d_Y_loss: 0.1851 | g_total_loss: 5.3562
Saved samples_cyclegan/sample-002600-X-Y.png
Saved samples_cyclegan/sample-002600-Y-X.png
Epoch [ 2610/ 5000] | d_X_loss: 0.1845 | d_Y_loss: 0.1958 | g_total_loss: 4.8707
Epoch [ 2620/ 5000] | d_X_loss: 0.2178 | d_Y_loss: 0.1989 | g_total_loss: 5.2634
Epoch [ 2630/ 5000] | d_X_loss: 0.3365 | d_Y_loss: 0.1723 | g_total_loss: 4.9907
Epoch [ 2640/ 5000] | d_X_loss: 0.1818 | d_Y_loss: 0.1595 | g_total_loss: 5.4925
Epoch [ 2650/ 5000] | d_X_loss: 0.2059 | d_Y_loss: 0.1355 | g_total_loss: 5.4577
Epoch [ 2660/ 5000] | d_X_loss: 0.2371 | d_Y_loss: 0.0961 | g_total_loss: 5.2258
Epoch [ 2670/ 5000] | d_X_loss: 0.2567 | d_Y_loss: 0.4553 | g_total_loss: 5.0931
Epoch [ 2680/ 5000] | d_X_loss: 0.2276 | d_Y_loss: 0.2250 | g_total_loss: 4.7135
Epoch [ 2690/ 5000] | d_X_loss: 0.2470 | d_Y_loss: 0.1306 | g_total_loss: 5.0719
Epoch [ 2700/ 5000] | d_X_loss: 0.2074 | d_Y_loss: 0.2037 | g_total_loss: 5.2337
Saved samples_cyclegan/sample-002700-X-Y.png
Saved samples_cyclegan/sample-002700-Y-X.png
Epoch [ 2710/ 5000] | d_X_loss: 0.1844 | d_Y_loss: 0.1662 | g_total_loss: 4.7837
Epoch [ 2720/ 5000] | d_X_loss: 0.1464 | d_Y_loss: 0.3277 | g_total_loss: 4.6002
Epoch [ 2730/ 5000] | d_X_loss: 0.2379 | d_Y_loss: 0.2597 | g_total_loss: 5.1693
Epoch [ 2740/ 5000] | d_X_loss: 0.1375 | d_Y_loss: 0.1248 | g_total_loss: 4.7133
Epoch [ 2750/ 5000] | d_X_loss: 0.2710 | d_Y_loss: 0.1880 | g_total_loss: 5.4363
Epoch [ 2760/ 5000] | d_X_loss: 0.1829 | d_Y_loss: 0.1880 | g_total_loss: 4.9460
Epoch [ 2770/ 5000] | d_X_loss: 0.2098 | d_Y_loss: 0.1062 | g_total_loss: 5.0045
Epoch [ 2780/ 5000] | d_X_loss: 0.3195 | d_Y_loss: 0.2966 | g_total_loss: 4.8062
Epoch [ 2790/ 5000] | d_X_loss: 0.2759 | d_Y_loss: 0.2903 | g_total_loss: 4.4209
Epoch [ 2800/ 5000] | d_X_loss: 0.2334 | d_Y_loss: 0.1179 | g_total_loss: 4.7782
Saved samples_cyclegan/sample-002800-X-Y.png
Saved samples_cyclegan/sample-002800-Y-X.png
Epoch [ 2810/ 5000] | d_X_loss: 0.1188 | d_Y_loss: 0.1730 | g_total_loss: 5.9629
Epoch [ 2820/ 5000] | d_X_loss: 0.1793 | d_Y_loss: 0.2476 | g_total_loss: 4.5203
Epoch [ 2830/ 5000] | d_X_loss: 0.1846 | d_Y_loss: 0.1843 | g_total_loss: 5.1254
Epoch [ 2840/ 5000] | d_X_loss: 0.2477 | d_Y_loss: 0.1660 | g_total_loss: 5.8213
Epoch [ 2850/ 5000] | d_X_loss: 0.1881 | d_Y_loss: 0.1942 | g_total_loss: 5.5033
Epoch [ 2860/ 5000] | d_X_loss: 0.4152 | d_Y_loss: 0.2507 | g_total_loss: 4.4770
Epoch [ 2870/ 5000] | d_X_loss: 0.2165 | d_Y_loss: 0.2171 | g_total_loss: 5.4253
Epoch [ 2880/ 5000] | d_X_loss: 0.1606 | d_Y_loss: 0.2056 | g_total_loss: 4.3657
Epoch [ 2890/ 5000] | d_X_loss: 0.1646 | d_Y_loss: 0.1213 | g_total_loss: 5.4652
Epoch [ 2900/ 5000] | d_X_loss: 0.1579 | d_Y_loss: 0.3154 | g_total_loss: 4.2023
Saved samples_cyclegan/sample-002900-X-Y.png
Saved samples_cyclegan/sample-002900-Y-X.png
Epoch [ 2910/ 5000] | d_X_loss: 0.2451 | d_Y_loss: 0.1864 | g_total_loss: 5.3189
Epoch [ 2920/ 5000] | d_X_loss: 0.3489 | d_Y_loss: 0.2077 | g_total_loss: 5.5187
Epoch [ 2930/ 5000] | d_X_loss: 0.0784 | d_Y_loss: 0.1139 | g_total_loss: 5.0993
Epoch [ 2940/ 5000] | d_X_loss: 0.2031 | d_Y_loss: 0.1109 | g_total_loss: 5.8878
Epoch [ 2950/ 5000] | d_X_loss: 0.2489 | d_Y_loss: 0.1971 | g_total_loss: 5.8869
Epoch [ 2960/ 5000] | d_X_loss: 0.1914 | d_Y_loss: 0.2249 | g_total_loss: 4.8232
Epoch [ 2970/ 5000] | d_X_loss: 0.2056 | d_Y_loss: 0.2406 | g_total_loss: 4.4763
Epoch [ 2980/ 5000] | d_X_loss: 0.1522 | d_Y_loss: 0.1017 | g_total_loss: 5.0003
Epoch [ 2990/ 5000] | d_X_loss: 0.1324 | d_Y_loss: 0.2409 | g_total_loss: 5.4133
Epoch [ 3000/ 5000] | d_X_loss: 0.1413 | d_Y_loss: 0.2280 | g_total_loss: 4.6270
Saved samples_cyclegan/sample-003000-X-Y.png
Saved samples_cyclegan/sample-003000-Y-X.png
Epoch [ 3010/ 5000] | d_X_loss: 0.1374 | d_Y_loss: 0.2673 | g_total_loss: 4.7117
Epoch [ 3020/ 5000] | d_X_loss: 0.1758 | d_Y_loss: 0.2097 | g_total_loss: 5.1432
Epoch [ 3030/ 5000] | d_X_loss: 0.2296 | d_Y_loss: 0.2123 | g_total_loss: 4.7429
Epoch [ 3040/ 5000] | d_X_loss: 0.4535 | d_Y_loss: 0.1856 | g_total_loss: 3.8527
Epoch [ 3050/ 5000] | d_X_loss: 0.2229 | d_Y_loss: 0.1542 | g_total_loss: 5.6249
Epoch [ 3060/ 5000] | d_X_loss: 0.1709 | d_Y_loss: 0.2125 | g_total_loss: 4.7858
Epoch [ 3070/ 5000] | d_X_loss: 0.3041 | d_Y_loss: 0.1566 | g_total_loss: 4.3003
Epoch [ 3080/ 5000] | d_X_loss: 0.0921 | d_Y_loss: 0.2381 | g_total_loss: 4.8379
Epoch [ 3090/ 5000] | d_X_loss: 0.2477 | d_Y_loss: 0.2026 | g_total_loss: 4.4939
Epoch [ 3100/ 5000] | d_X_loss: 0.1678 | d_Y_loss: 0.1657 | g_total_loss: 5.5775
Saved samples_cyclegan/sample-003100-X-Y.png
Saved samples_cyclegan/sample-003100-Y-X.png
Epoch [ 3110/ 5000] | d_X_loss: 0.3103 | d_Y_loss: 0.0960 | g_total_loss: 4.6929
Epoch [ 3120/ 5000] | d_X_loss: 0.1566 | d_Y_loss: 0.2046 | g_total_loss: 6.0648
Epoch [ 3130/ 5000] | d_X_loss: 0.2217 | d_Y_loss: 0.1679 | g_total_loss: 4.7411
Epoch [ 3140/ 5000] | d_X_loss: 0.1488 | d_Y_loss: 0.2091 | g_total_loss: 4.9359
Epoch [ 3150/ 5000] | d_X_loss: 0.1803 | d_Y_loss: 0.1767 | g_total_loss: 5.2933
Epoch [ 3160/ 5000] | d_X_loss: 0.2329 | d_Y_loss: 0.0889 | g_total_loss: 5.5365
Epoch [ 3170/ 5000] | d_X_loss: 0.2194 | d_Y_loss: 0.0846 | g_total_loss: 5.3288
Epoch [ 3180/ 5000] | d_X_loss: 0.1623 | d_Y_loss: 0.1868 | g_total_loss: 5.0355
Epoch [ 3190/ 5000] | d_X_loss: 0.1542 | d_Y_loss: 0.1472 | g_total_loss: 4.8902
Epoch [ 3200/ 5000] | d_X_loss: 0.2493 | d_Y_loss: 0.1037 | g_total_loss: 5.0512
Saved samples_cyclegan/sample-003200-X-Y.png
Saved samples_cyclegan/sample-003200-Y-X.png
Epoch [ 3210/ 5000] | d_X_loss: 0.2260 | d_Y_loss: 0.2096 | g_total_loss: 4.2392
Epoch [ 3220/ 5000] | d_X_loss: 0.1524 | d_Y_loss: 0.1904 | g_total_loss: 4.9391
Epoch [ 3230/ 5000] | d_X_loss: 0.1460 | d_Y_loss: 0.0846 | g_total_loss: 4.9916
Epoch [ 3240/ 5000] | d_X_loss: 0.1558 | d_Y_loss: 0.1385 | g_total_loss: 5.1526
Epoch [ 3250/ 5000] | d_X_loss: 0.1480 | d_Y_loss: 0.1083 | g_total_loss: 5.3679
Epoch [ 3260/ 5000] | d_X_loss: 0.3110 | d_Y_loss: 0.1289 | g_total_loss: 4.3622
Epoch [ 3270/ 5000] | d_X_loss: 0.1460 | d_Y_loss: 0.2608 | g_total_loss: 4.5615
Epoch [ 3280/ 5000] | d_X_loss: 0.2061 | d_Y_loss: 0.1456 | g_total_loss: 5.5635
Epoch [ 3290/ 5000] | d_X_loss: 0.1764 | d_Y_loss: 0.2183 | g_total_loss: 5.0062
Epoch [ 3300/ 5000] | d_X_loss: 0.1595 | d_Y_loss: 0.2436 | g_total_loss: 4.4924
Saved samples_cyclegan/sample-003300-X-Y.png
Saved samples_cyclegan/sample-003300-Y-X.png
Epoch [ 3310/ 5000] | d_X_loss: 0.2091 | d_Y_loss: 0.2181 | g_total_loss: 5.0006
Epoch [ 3320/ 5000] | d_X_loss: 0.2031 | d_Y_loss: 0.2117 | g_total_loss: 5.0135
Epoch [ 3330/ 5000] | d_X_loss: 0.1838 | d_Y_loss: 0.1495 | g_total_loss: 4.5231
Epoch [ 3340/ 5000] | d_X_loss: 0.2210 | d_Y_loss: 0.1362 | g_total_loss: 4.8738
Epoch [ 3350/ 5000] | d_X_loss: 0.1329 | d_Y_loss: 0.2451 | g_total_loss: 4.6245
Epoch [ 3360/ 5000] | d_X_loss: 0.3196 | d_Y_loss: 0.1097 | g_total_loss: 5.1607
Epoch [ 3370/ 5000] | d_X_loss: 0.3235 | d_Y_loss: 0.1448 | g_total_loss: 6.0945
Epoch [ 3380/ 5000] | d_X_loss: 0.1421 | d_Y_loss: 0.1638 | g_total_loss: 5.1618
Epoch [ 3390/ 5000] | d_X_loss: 0.2747 | d_Y_loss: 0.0722 | g_total_loss: 6.1626
Epoch [ 3400/ 5000] | d_X_loss: 0.1656 | d_Y_loss: 0.1083 | g_total_loss: 4.8993
Saved samples_cyclegan/sample-003400-X-Y.png
Saved samples_cyclegan/sample-003400-Y-X.png
Epoch [ 3410/ 5000] | d_X_loss: 0.0957 | d_Y_loss: 0.1025 | g_total_loss: 5.9145
Epoch [ 3420/ 5000] | d_X_loss: 0.2088 | d_Y_loss: 0.2713 | g_total_loss: 4.9380
Epoch [ 3430/ 5000] | d_X_loss: 0.3261 | d_Y_loss: 0.1905 | g_total_loss: 4.6565
Epoch [ 3440/ 5000] | d_X_loss: 0.1740 | d_Y_loss: 0.2217 | g_total_loss: 5.3208
Epoch [ 3450/ 5000] | d_X_loss: 0.1822 | d_Y_loss: 0.1716 | g_total_loss: 4.7729
Epoch [ 3460/ 5000] | d_X_loss: 0.2297 | d_Y_loss: 0.1066 | g_total_loss: 5.7283
Epoch [ 3470/ 5000] | d_X_loss: 0.1824 | d_Y_loss: 0.1342 | g_total_loss: 4.3941
Epoch [ 3480/ 5000] | d_X_loss: 0.1052 | d_Y_loss: 0.1427 | g_total_loss: 5.3567
Epoch [ 3490/ 5000] | d_X_loss: 0.1454 | d_Y_loss: 0.1037 | g_total_loss: 5.3857
Epoch [ 3500/ 5000] | d_X_loss: 0.1631 | d_Y_loss: 0.1860 | g_total_loss: 5.1682
Saved samples_cyclegan/sample-003500-X-Y.png
Saved samples_cyclegan/sample-003500-Y-X.png
Epoch [ 3510/ 5000] | d_X_loss: 0.2548 | d_Y_loss: 0.1138 | g_total_loss: 6.0424
Epoch [ 3520/ 5000] | d_X_loss: 0.1217 | d_Y_loss: 0.2236 | g_total_loss: 4.2190
Epoch [ 3530/ 5000] | d_X_loss: 0.1423 | d_Y_loss: 0.1942 | g_total_loss: 6.5445
Epoch [ 3540/ 5000] | d_X_loss: 0.1014 | d_Y_loss: 0.1476 | g_total_loss: 5.5012
Epoch [ 3550/ 5000] | d_X_loss: 0.1578 | d_Y_loss: 0.1183 | g_total_loss: 4.9497
Epoch [ 3560/ 5000] | d_X_loss: 0.0710 | d_Y_loss: 0.2118 | g_total_loss: 5.0288
Epoch [ 3570/ 5000] | d_X_loss: 0.2760 | d_Y_loss: 0.1354 | g_total_loss: 6.1434
Epoch [ 3580/ 5000] | d_X_loss: 0.2391 | d_Y_loss: 0.1657 | g_total_loss: 5.1402
Epoch [ 3590/ 5000] | d_X_loss: 0.1680 | d_Y_loss: 0.1314 | g_total_loss: 5.9905
Epoch [ 3600/ 5000] | d_X_loss: 0.2876 | d_Y_loss: 0.1282 | g_total_loss: 4.3332
Saved samples_cyclegan/sample-003600-X-Y.png
Saved samples_cyclegan/sample-003600-Y-X.png
Epoch [ 3610/ 5000] | d_X_loss: 0.1531 | d_Y_loss: 0.1698 | g_total_loss: 4.9632
Epoch [ 3620/ 5000] | d_X_loss: 0.1794 | d_Y_loss: 0.1765 | g_total_loss: 4.6514
Epoch [ 3630/ 5000] | d_X_loss: 0.1221 | d_Y_loss: 0.1639 | g_total_loss: 4.9552
Epoch [ 3640/ 5000] | d_X_loss: 0.1177 | d_Y_loss: 0.2377 | g_total_loss: 6.0496
Epoch [ 3650/ 5000] | d_X_loss: 0.2483 | d_Y_loss: 0.2198 | g_total_loss: 4.4872
Epoch [ 3660/ 5000] | d_X_loss: 0.1947 | d_Y_loss: 0.2574 | g_total_loss: 6.8336
Epoch [ 3670/ 5000] | d_X_loss: 0.1983 | d_Y_loss: 0.1525 | g_total_loss: 5.2457
Epoch [ 3680/ 5000] | d_X_loss: 0.1526 | d_Y_loss: 0.1395 | g_total_loss: 4.3871
Epoch [ 3690/ 5000] | d_X_loss: 0.1034 | d_Y_loss: 0.1089 | g_total_loss: 5.4499
Epoch [ 3700/ 5000] | d_X_loss: 0.2031 | d_Y_loss: 0.1153 | g_total_loss: 4.4038
Saved samples_cyclegan/sample-003700-X-Y.png
Saved samples_cyclegan/sample-003700-Y-X.png
Epoch [ 3710/ 5000] | d_X_loss: 0.1577 | d_Y_loss: 0.1608 | g_total_loss: 4.9944
Epoch [ 3720/ 5000] | d_X_loss: 0.1432 | d_Y_loss: 0.2772 | g_total_loss: 6.6016
Epoch [ 3730/ 5000] | d_X_loss: 0.1808 | d_Y_loss: 0.1375 | g_total_loss: 5.2673
Epoch [ 3740/ 5000] | d_X_loss: 0.0708 | d_Y_loss: 0.1273 | g_total_loss: 5.2124
Epoch [ 3750/ 5000] | d_X_loss: 0.1106 | d_Y_loss: 0.1069 | g_total_loss: 5.0497
Epoch [ 3760/ 5000] | d_X_loss: 0.1097 | d_Y_loss: 0.1054 | g_total_loss: 5.7989
Epoch [ 3770/ 5000] | d_X_loss: 0.1204 | d_Y_loss: 0.1195 | g_total_loss: 5.4080
Epoch [ 3780/ 5000] | d_X_loss: 0.1039 | d_Y_loss: 0.1679 | g_total_loss: 4.7856
Epoch [ 3790/ 5000] | d_X_loss: 0.1050 | d_Y_loss: 0.0994 | g_total_loss: 5.1986
Epoch [ 3800/ 5000] | d_X_loss: 0.3427 | d_Y_loss: 0.1208 | g_total_loss: 5.7180
Saved samples_cyclegan/sample-003800-X-Y.png
Saved samples_cyclegan/sample-003800-Y-X.png
Epoch [ 3810/ 5000] | d_X_loss: 0.1373 | d_Y_loss: 0.1300 | g_total_loss: 4.7854
Epoch [ 3820/ 5000] | d_X_loss: 0.2328 | d_Y_loss: 0.1269 | g_total_loss: 4.5389
Epoch [ 3830/ 5000] | d_X_loss: 0.1185 | d_Y_loss: 0.1740 | g_total_loss: 5.7115
Epoch [ 3840/ 5000] | d_X_loss: 0.1286 | d_Y_loss: 0.1379 | g_total_loss: 5.3478
Epoch [ 3850/ 5000] | d_X_loss: 0.1662 | d_Y_loss: 0.2782 | g_total_loss: 6.0479
Epoch [ 3860/ 5000] | d_X_loss: 0.1582 | d_Y_loss: 0.1256 | g_total_loss: 5.3731
Epoch [ 3870/ 5000] | d_X_loss: 0.1287 | d_Y_loss: 0.0783 | g_total_loss: 5.6529
Epoch [ 3880/ 5000] | d_X_loss: 0.2226 | d_Y_loss: 0.0985 | g_total_loss: 4.4430
Epoch [ 3890/ 5000] | d_X_loss: 0.1107 | d_Y_loss: 0.1554 | g_total_loss: 4.7830
Epoch [ 3900/ 5000] | d_X_loss: 0.3342 | d_Y_loss: 0.1132 | g_total_loss: 5.4524
Saved samples_cyclegan/sample-003900-X-Y.png
Saved samples_cyclegan/sample-003900-Y-X.png
Epoch [ 3910/ 5000] | d_X_loss: 0.1487 | d_Y_loss: 0.1755 | g_total_loss: 4.7598
Epoch [ 3920/ 5000] | d_X_loss: 0.1057 | d_Y_loss: 0.1380 | g_total_loss: 5.4653
Epoch [ 3930/ 5000] | d_X_loss: 0.1944 | d_Y_loss: 0.1684 | g_total_loss: 4.3728
Epoch [ 3940/ 5000] | d_X_loss: 0.1154 | d_Y_loss: 0.1317 | g_total_loss: 5.8253
Epoch [ 3950/ 5000] | d_X_loss: 0.2105 | d_Y_loss: 0.1290 | g_total_loss: 5.0075
Epoch [ 3960/ 5000] | d_X_loss: 0.1271 | d_Y_loss: 0.1067 | g_total_loss: 4.5757
Epoch [ 3970/ 5000] | d_X_loss: 0.1836 | d_Y_loss: 0.1191 | g_total_loss: 5.1151
Epoch [ 3980/ 5000] | d_X_loss: 0.1077 | d_Y_loss: 0.1152 | g_total_loss: 4.9800
Epoch [ 3990/ 5000] | d_X_loss: 0.1471 | d_Y_loss: 0.0909 | g_total_loss: 5.5206
Epoch [ 4000/ 5000] | d_X_loss: 0.1528 | d_Y_loss: 0.0684 | g_total_loss: 5.2811
Saved samples_cyclegan/sample-004000-X-Y.png
Saved samples_cyclegan/sample-004000-Y-X.png
Epoch [ 4010/ 5000] | d_X_loss: 0.0924 | d_Y_loss: 0.1295 | g_total_loss: 4.9092
Epoch [ 4020/ 5000] | d_X_loss: 0.1566 | d_Y_loss: 0.1283 | g_total_loss: 4.8415
Epoch [ 4030/ 5000] | d_X_loss: 0.3518 | d_Y_loss: 0.1116 | g_total_loss: 4.5638
Epoch [ 4040/ 5000] | d_X_loss: 0.1950 | d_Y_loss: 0.0806 | g_total_loss: 5.7276
Epoch [ 4050/ 5000] | d_X_loss: 0.1594 | d_Y_loss: 0.0558 | g_total_loss: 5.0399
Epoch [ 4060/ 5000] | d_X_loss: 0.1131 | d_Y_loss: 0.0821 | g_total_loss: 5.2717
Epoch [ 4070/ 5000] | d_X_loss: 0.0737 | d_Y_loss: 0.1242 | g_total_loss: 4.8895
Epoch [ 4080/ 5000] | d_X_loss: 0.2395 | d_Y_loss: 0.0799 | g_total_loss: 4.9505
Epoch [ 4090/ 5000] | d_X_loss: 0.0916 | d_Y_loss: 0.2028 | g_total_loss: 5.7421
Epoch [ 4100/ 5000] | d_X_loss: 0.1407 | d_Y_loss: 0.1082 | g_total_loss: 4.9883
Saved samples_cyclegan/sample-004100-X-Y.png
Saved samples_cyclegan/sample-004100-Y-X.png
Epoch [ 4110/ 5000] | d_X_loss: 0.1422 | d_Y_loss: 0.2060 | g_total_loss: 5.3523
Epoch [ 4120/ 5000] | d_X_loss: 0.1268 | d_Y_loss: 0.1341 | g_total_loss: 4.5352
Epoch [ 4130/ 5000] | d_X_loss: 0.1195 | d_Y_loss: 0.1325 | g_total_loss: 5.4460
Epoch [ 4140/ 5000] | d_X_loss: 0.1953 | d_Y_loss: 0.0967 | g_total_loss: 5.1094
Epoch [ 4150/ 5000] | d_X_loss: 0.1276 | d_Y_loss: 0.1373 | g_total_loss: 4.9153
Epoch [ 4160/ 5000] | d_X_loss: 0.1628 | d_Y_loss: 0.1454 | g_total_loss: 4.7484
Epoch [ 4170/ 5000] | d_X_loss: 0.2331 | d_Y_loss: 0.1197 | g_total_loss: 5.0998
Epoch [ 4180/ 5000] | d_X_loss: 0.1398 | d_Y_loss: 0.1304 | g_total_loss: 4.5147
Epoch [ 4190/ 5000] | d_X_loss: 0.1525 | d_Y_loss: 0.1390 | g_total_loss: 5.7495
Epoch [ 4200/ 5000] | d_X_loss: 0.1661 | d_Y_loss: 0.1326 | g_total_loss: 4.6503
Saved samples_cyclegan/sample-004200-X-Y.png
Saved samples_cyclegan/sample-004200-Y-X.png
Epoch [ 4210/ 5000] | d_X_loss: 0.1702 | d_Y_loss: 0.5106 | g_total_loss: 4.0017
Epoch [ 4220/ 5000] | d_X_loss: 0.1023 | d_Y_loss: 0.1332 | g_total_loss: 5.2717
Epoch [ 4230/ 5000] | d_X_loss: 0.0947 | d_Y_loss: 0.1124 | g_total_loss: 5.4561
Epoch [ 4240/ 5000] | d_X_loss: 0.1562 | d_Y_loss: 0.1354 | g_total_loss: 4.5154
Epoch [ 4250/ 5000] | d_X_loss: 0.1160 | d_Y_loss: 0.1290 | g_total_loss: 5.3586
Epoch [ 4260/ 5000] | d_X_loss: 0.0983 | d_Y_loss: 0.0962 | g_total_loss: 5.6302
Epoch [ 4270/ 5000] | d_X_loss: 0.1773 | d_Y_loss: 0.1087 | g_total_loss: 6.3955
Epoch [ 4280/ 5000] | d_X_loss: 0.1349 | d_Y_loss: 0.0898 | g_total_loss: 5.4002
Epoch [ 4290/ 5000] | d_X_loss: 0.1147 | d_Y_loss: 0.0884 | g_total_loss: 4.9216
Epoch [ 4300/ 5000] | d_X_loss: 0.1485 | d_Y_loss: 0.1898 | g_total_loss: 4.5741
Saved samples_cyclegan/sample-004300-X-Y.png
Saved samples_cyclegan/sample-004300-Y-X.png
Epoch [ 4310/ 5000] | d_X_loss: 0.1118 | d_Y_loss: 0.2238 | g_total_loss: 5.4052
Epoch [ 4320/ 5000] | d_X_loss: 0.1078 | d_Y_loss: 0.1051 | g_total_loss: 4.9165
Epoch [ 4330/ 5000] | d_X_loss: 0.1757 | d_Y_loss: 0.2088 | g_total_loss: 6.2531
Epoch [ 4340/ 5000] | d_X_loss: 0.1145 | d_Y_loss: 0.0782 | g_total_loss: 5.1854
Epoch [ 4350/ 5000] | d_X_loss: 0.1405 | d_Y_loss: 0.0994 | g_total_loss: 4.6656
Epoch [ 4360/ 5000] | d_X_loss: 0.1675 | d_Y_loss: 0.0995 | g_total_loss: 5.1420
Epoch [ 4370/ 5000] | d_X_loss: 0.0881 | d_Y_loss: 0.0782 | g_total_loss: 5.6078
Epoch [ 4380/ 5000] | d_X_loss: 0.0708 | d_Y_loss: 0.1198 | g_total_loss: 5.2125
Epoch [ 4390/ 5000] | d_X_loss: 0.2244 | d_Y_loss: 0.1046 | g_total_loss: 5.1747
Epoch [ 4400/ 5000] | d_X_loss: 0.1808 | d_Y_loss: 0.0784 | g_total_loss: 4.6094
Saved samples_cyclegan/sample-004400-X-Y.png
Saved samples_cyclegan/sample-004400-Y-X.png
Epoch [ 4410/ 5000] | d_X_loss: 0.1064 | d_Y_loss: 0.1441 | g_total_loss: 5.2000
Epoch [ 4420/ 5000] | d_X_loss: 0.2207 | d_Y_loss: 0.0943 | g_total_loss: 4.7673
Epoch [ 4430/ 5000] | d_X_loss: 0.0754 | d_Y_loss: 0.0802 | g_total_loss: 5.0550
Epoch [ 4440/ 5000] | d_X_loss: 0.1462 | d_Y_loss: 0.2645 | g_total_loss: 4.0645
Epoch [ 4450/ 5000] | d_X_loss: 0.1039 | d_Y_loss: 0.0704 | g_total_loss: 5.2662
Epoch [ 4460/ 5000] | d_X_loss: 0.2617 | d_Y_loss: 0.1524 | g_total_loss: 4.0385
Epoch [ 4470/ 5000] | d_X_loss: 0.1136 | d_Y_loss: 0.1196 | g_total_loss: 5.2618
Epoch [ 4480/ 5000] | d_X_loss: 0.1113 | d_Y_loss: 0.1221 | g_total_loss: 5.5019
Epoch [ 4490/ 5000] | d_X_loss: 0.0837 | d_Y_loss: 0.1096 | g_total_loss: 4.8734
Epoch [ 4500/ 5000] | d_X_loss: 0.1294 | d_Y_loss: 0.0825 | g_total_loss: 6.0323
Saved samples_cyclegan/sample-004500-X-Y.png
Saved samples_cyclegan/sample-004500-Y-X.png
Epoch [ 4510/ 5000] | d_X_loss: 0.1320 | d_Y_loss: 0.1376 | g_total_loss: 5.2206
Epoch [ 4520/ 5000] | d_X_loss: 0.1565 | d_Y_loss: 0.1492 | g_total_loss: 4.4485
Epoch [ 4530/ 5000] | d_X_loss: 0.0950 | d_Y_loss: 0.1642 | g_total_loss: 5.0441
Epoch [ 4540/ 5000] | d_X_loss: 0.1737 | d_Y_loss: 0.1191 | g_total_loss: 4.5577
Epoch [ 4550/ 5000] | d_X_loss: 0.1265 | d_Y_loss: 0.0694 | g_total_loss: 4.8985
Epoch [ 4560/ 5000] | d_X_loss: 0.1053 | d_Y_loss: 0.0814 | g_total_loss: 4.6301
Epoch [ 4570/ 5000] | d_X_loss: 0.1474 | d_Y_loss: 0.0674 | g_total_loss: 5.4452
Epoch [ 4580/ 5000] | d_X_loss: 0.0945 | d_Y_loss: 0.1355 | g_total_loss: 4.4737
Epoch [ 4590/ 5000] | d_X_loss: 0.0875 | d_Y_loss: 0.0988 | g_total_loss: 4.6843
Epoch [ 4600/ 5000] | d_X_loss: 0.0968 | d_Y_loss: 0.1333 | g_total_loss: 4.9640
Saved samples_cyclegan/sample-004600-X-Y.png
Saved samples_cyclegan/sample-004600-Y-X.png
Epoch [ 4610/ 5000] | d_X_loss: 0.2836 | d_Y_loss: 0.0729 | g_total_loss: 5.7317
Epoch [ 4620/ 5000] | d_X_loss: 0.2762 | d_Y_loss: 0.1371 | g_total_loss: 3.9986
Epoch [ 4630/ 5000] | d_X_loss: 0.1166 | d_Y_loss: 0.1031 | g_total_loss: 4.6918
Epoch [ 4640/ 5000] | d_X_loss: 0.1254 | d_Y_loss: 0.0688 | g_total_loss: 4.7678
Epoch [ 4650/ 5000] | d_X_loss: 0.1455 | d_Y_loss: 0.0894 | g_total_loss: 5.5635
Epoch [ 4660/ 5000] | d_X_loss: 0.1139 | d_Y_loss: 0.0875 | g_total_loss: 5.1098
Epoch [ 4670/ 5000] | d_X_loss: 0.0943 | d_Y_loss: 0.0668 | g_total_loss: 5.1865
Epoch [ 4680/ 5000] | d_X_loss: 0.0583 | d_Y_loss: 0.0595 | g_total_loss: 5.1964
Epoch [ 4690/ 5000] | d_X_loss: 0.0735 | d_Y_loss: 0.1664 | g_total_loss: 4.6178
Epoch [ 4700/ 5000] | d_X_loss: 0.2489 | d_Y_loss: 0.1300 | g_total_loss: 4.6606
Saved samples_cyclegan/sample-004700-X-Y.png
Saved samples_cyclegan/sample-004700-Y-X.png
Epoch [ 4710/ 5000] | d_X_loss: 0.0841 | d_Y_loss: 0.1064 | g_total_loss: 4.4940
Epoch [ 4720/ 5000] | d_X_loss: 0.1187 | d_Y_loss: 0.1116 | g_total_loss: 5.6518
Epoch [ 4730/ 5000] | d_X_loss: 0.0736 | d_Y_loss: 0.1472 | g_total_loss: 4.7207
Epoch [ 4740/ 5000] | d_X_loss: 0.0910 | d_Y_loss: 0.0740 | g_total_loss: 5.0659
Epoch [ 4750/ 5000] | d_X_loss: 0.1237 | d_Y_loss: 0.0720 | g_total_loss: 5.3202
Epoch [ 4760/ 5000] | d_X_loss: 0.0842 | d_Y_loss: 0.3616 | g_total_loss: 5.8229
Epoch [ 4770/ 5000] | d_X_loss: 0.1363 | d_Y_loss: 0.0967 | g_total_loss: 4.8909
Epoch [ 4780/ 5000] | d_X_loss: 0.1281 | d_Y_loss: 0.1729 | g_total_loss: 4.5673
Epoch [ 4790/ 5000] | d_X_loss: 0.1142 | d_Y_loss: 0.1356 | g_total_loss: 4.7692
Epoch [ 4800/ 5000] | d_X_loss: 0.1332 | d_Y_loss: 0.0957 | g_total_loss: 5.8508
Saved samples_cyclegan/sample-004800-X-Y.png
Saved samples_cyclegan/sample-004800-Y-X.png
Epoch [ 4810/ 5000] | d_X_loss: 0.1287 | d_Y_loss: 0.0965 | g_total_loss: 5.0600
Epoch [ 4820/ 5000] | d_X_loss: 0.1975 | d_Y_loss: 0.1831 | g_total_loss: 4.6138
Epoch [ 4830/ 5000] | d_X_loss: 0.1987 | d_Y_loss: 0.0715 | g_total_loss: 5.3598
Epoch [ 4840/ 5000] | d_X_loss: 0.1069 | d_Y_loss: 0.1744 | g_total_loss: 5.6198
Epoch [ 4850/ 5000] | d_X_loss: 0.0752 | d_Y_loss: 0.1103 | g_total_loss: 4.9979
Epoch [ 4860/ 5000] | d_X_loss: 0.0758 | d_Y_loss: 0.1118 | g_total_loss: 5.2497
Epoch [ 4870/ 5000] | d_X_loss: 0.1625 | d_Y_loss: 0.0953 | g_total_loss: 5.5657
Epoch [ 4880/ 5000] | d_X_loss: 0.0876 | d_Y_loss: 0.3919 | g_total_loss: 6.5826
Epoch [ 4890/ 5000] | d_X_loss: 0.1148 | d_Y_loss: 0.0945 | g_total_loss: 5.4998
Epoch [ 4900/ 5000] | d_X_loss: 0.1953 | d_Y_loss: 0.0895 | g_total_loss: 4.7649
Saved samples_cyclegan/sample-004900-X-Y.png
Saved samples_cyclegan/sample-004900-Y-X.png
Epoch [ 4910/ 5000] | d_X_loss: 0.0801 | d_Y_loss: 0.1752 | g_total_loss: 5.8803
Epoch [ 4920/ 5000] | d_X_loss: 0.1586 | d_Y_loss: 0.0722 | g_total_loss: 4.9957
Epoch [ 4930/ 5000] | d_X_loss: 0.1689 | d_Y_loss: 0.0598 | g_total_loss: 5.2123
Epoch [ 4940/ 5000] | d_X_loss: 0.0442 | d_Y_loss: 0.2826 | g_total_loss: 5.5463
Epoch [ 4950/ 5000] | d_X_loss: 0.0832 | d_Y_loss: 0.1008 | g_total_loss: 5.0017
Epoch [ 4960/ 5000] | d_X_loss: 0.1966 | d_Y_loss: 0.0936 | g_total_loss: 5.0535
Epoch [ 4970/ 5000] | d_X_loss: 0.1340 | d_Y_loss: 0.0695 | g_total_loss: 5.4168
Epoch [ 4980/ 5000] | d_X_loss: 0.1126 | d_Y_loss: 0.0806 | g_total_loss: 6.2162
Epoch [ 4990/ 5000] | d_X_loss: 0.0886 | d_Y_loss: 0.1079 | g_total_loss: 5.1975
Epoch [ 5000/ 5000] | d_X_loss: 0.1586 | d_Y_loss: 0.0648 | g_total_loss: 4.5495
Saved samples_cyclegan/sample-005000-X-Y.png
Saved samples_cyclegan/sample-005000-Y-X.png

Tips on Training and Loss Patterns

A lot of experimentation goes into finding the best hyperparameters such that the generators and discriminators don't overpower each other. It's often a good starting point to look at existing papers to find what has worked in previous experiments, I'd recommend this DCGAN paper in addition to the original CycleGAN paper to see what worked for them. Then, you can try your own experiments based off of a good foundation.

Discriminator Losses

When you display the generator and discriminator losses you should see that there is always some discriminator loss; recall that we are trying to design a model that can generate good "fake" images. So, the ideal discriminator will not be able to tell the difference between real and fake images and, as such, will always have some loss. You should also see that $D_X$ and $D_Y$ are roughly at the same loss levels; if they are not, this indicates that your training is favoring one type of discriminator over the other and you may need to look at biases in your models or data.

Generator Loss

The generator's loss should start significantly higher than the discriminator losses because it is accounting for the loss of both generators and weighted reconstruction errors. You should see this loss decrease a lot at the start of training because initial, generated images are often far-off from being good fakes. After some time it may level off; this is normal since the generator and discriminator are both improving as they train. If you see that the loss is jumping around a lot, over time, you may want to try decreasing your learning rates or changing your cycle consistency loss to be a little more/less weighted.

In [27]:
fig, ax = plt.subplots(figsize=(12,8))
losses = np.array(losses)
plt.plot(losses.T[0], label='Discriminator, X', alpha=0.5)
plt.plot(losses.T[1], label='Discriminator, Y', alpha=0.5)
plt.plot(losses.T[2], label='Generators', alpha=0.5)
plt.title("Training Losses")
plt.legend()
Out[27]:
<matplotlib.legend.Legend at 0x7f1a6fec4400>

Evaluate the Result!

As you trained this model, you may have chosen to sample and save the results of your generated images after a certain number of training iterations. This gives you a way to see whether or not your Generators are creating good fake images. For example, the image below depicts real images in the $Y$ set, and the corresponding generated images during different points in the training process. You can see that the generator starts out creating very noisy, fake images, but begins to converge to better representations as it trains (though, not perfect).

Below, you've been given a helper function for displaying generated samples based on the passed in training iteration.

In [28]:
import matplotlib.image as mpimg

# helper visualization code
def view_samples(iteration, sample_dir='samples_cyclegan'):
    
    # samples are named by iteration
    path_XtoY = os.path.join(sample_dir, 'sample-{:06d}-X-Y.png'.format(iteration))
    path_YtoX = os.path.join(sample_dir, 'sample-{:06d}-Y-X.png'.format(iteration))
    
    # read in those samples
    try: 
        x2y = mpimg.imread(path_XtoY)
        y2x = mpimg.imread(path_YtoX)
    except:
        print('Invalid number of iterations.')
    
    fig, (ax1, ax2) = plt.subplots(figsize=(18,20), nrows=2, ncols=1, sharey=True, sharex=True)
    ax1.imshow(x2y)
    ax1.set_title('X to Y')
    ax2.imshow(y2x)
    ax2.set_title('Y to X')
In [29]:
# view samples at iteration 100
view_samples(100, 'samples_cyclegan')
In [31]:
# view samples at iteration 1000
view_samples(5000, 'samples_cyclegan')

Further Challenges and Directions

  • One shortcoming of this model is that it produces fairly low-resolution images; this is an ongoing area of research; you can read about a higher-resolution formulation that uses a multi-scale generator model, in this paper.
  • Relatedly, we may want to process these as larger (say 256x256) images at first, to take advantage of high-res data.
  • It may help your model to converge faster, if you initialize the weights in your network.
  • This model struggles with matching colors exactly. This is because, if $G_{YtoX}$ and $G_{XtoY}$ may change the tint of an image; the cycle consistency loss may not be affected and can still be small. You could choose to introduce a new, color-based loss term that compares $G_{YtoX}(y)$ and $y$, and $G_{XtoY}(x)$ and $x$, but then this becomes a supervised learning approach.
  • This unsupervised approach also struggles with geometric changes, like changing the apparent size of individual object in an image, so it is best suited for stylistic transformations.
  • For creating different kinds of models or trying out the Pix2Pix Architecture, this Github repository which implements CycleGAN and Pix2Pix in PyTorch is a great resource.

Once you are satified with your model, you are ancouraged to test it on a different dataset to see if it can find different types of mappings!


Different datasets for download

You can download a variety of datasets used in the Pix2Pix and CycleGAN papers, by following instructions in the associated Github repository. You'll just need to make sure that the data directories are named and organized correctly to load in that data.

In [ ]: